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( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 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.0f ); ///< 1. Highlight rendered (z-offset).
61 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 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(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
68 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< 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;
219 return Toolkit::TextInput::New();
222 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
224 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
225 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
226 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
227 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
228 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
229 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
233 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
234 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
235 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
236 PropertyRegistration property4( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
237 PropertyRegistration property5( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
238 PropertyRegistration property6( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
239 PropertyRegistration property7( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
240 PropertyRegistration property8( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
241 PropertyRegistration property9( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
244 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
246 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
248 QuadCoordinates quad(x1, y1, x2, y2);
249 mQuadList.push_back( quad );
252 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
254 for(std::size_t i = 0;i < mQuadList.size(); i++)
256 QuadCoordinates& quad = mQuadList[i];
258 quad.min.Clamp(min, max);
259 quad.max.Clamp(min, max);
263 // [TextInput] ////////////////////////////////////////////////////////////////
265 Dali::Toolkit::TextInput TextInput::New()
267 // Create the implementation
268 TextInputPtr textInput(new TextInput());
269 // Pass ownership to CustomActor via derived handle
270 Dali::Toolkit::TextInput handle(*textInput);
272 textInput->Initialize();
277 TextInput::TextInput()
278 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
283 mDisplayedTextView(),
284 mStyledPlaceHolderText(),
285 mMaxStringLength( DEFAULT_MAX_SIZE ),
286 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
287 mCursorPosition( 0 ),
288 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
289 mIsSelectionHandleOneFlipped( false ),
290 mIsSelectionHandleTwoFlipped( false ),
291 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
292 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
293 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
294 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
295 mSelectionHandleOnePosition( 0 ),
296 mSelectionHandleTwoPosition( 0 ),
298 mPreEditStartPosition( 0 ),
299 mPreEditLength ( 0 ),
300 mNumberOfSurroundingCharactersDeleted( 0 ),
301 mTouchStartTime( 0 ),
303 mCurrentCopySelecton(),
306 mScrollDisplacement(),
307 mCurrentHandlePosition(),
308 mCurrentSelectionId(),
309 mCurrentSelectionHandlePosition(),
310 mRequestedSelection( 0, 0 ),
311 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
312 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
314 mMaterialColor( LIGHTBLUE ),
315 mOverrideAutomaticAlignment( false ),
316 mCursorRTLEnabled( false ),
317 mClosestCursorPositionEOL ( false ),
318 mCursorBlinkStatus( true ),
319 mCursorVisibility( false ),
320 mGrabHandleVisibility( false ),
321 mIsCursorInScrollArea( true ),
322 mIsGrabHandleInScrollArea( true ),
323 mEditModeActive( false ),
324 mEditOnTouch( true ),
325 mTextSelection( true ),
326 mExceedEnabled( true ),
327 mGrabHandleEnabled( true ),
328 mIsSelectionHandleFlipEnabled( true ),
329 mPreEditFlag( false ),
330 mIgnoreCommitFlag( false ),
331 mIgnoreFirstCommitFlag( false ),
332 mSelectingText( false ),
333 mPreserveCursorPosition( false ),
334 mSelectTextOnCommit( false ),
335 mUnderlinedPriorToPreEdit ( false ),
336 mCommitByKeyInput( false ),
337 mPlaceHolderSet( false ),
338 mMarkUpEnabled( false )
340 // Updates the line height accordingly with the input style.
344 TextInput::~TextInput()
346 StopCursorBlinkTimer();
351 std::string TextInput::GetText() const
355 // Return text-view's text only if the text-input's text is not empty
356 // in order to not to return the placeholder text.
357 if( !mStyledText.empty() )
359 text = mDisplayedTextView.GetText();
365 std::string TextInput::GetMarkupText() const
367 std::string markupString;
368 MarkupProcessor::GetMarkupString( mStyledText, markupString );
373 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
375 // Get the placeholder styled text array from the markup string.
376 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
378 if( mStyledText.empty() )
380 // Set the placeholder text only if the styled text is empty.
381 mDisplayedTextView.SetText( mStyledPlaceHolderText );
382 mPlaceHolderSet = true;
386 std::string TextInput::GetPlaceholderText()
388 // Traverses the styled placeholder array getting only the text.
389 // Note that for some languages a 'character' could be represented by more than one 'char'
391 std::string placeholderText;
392 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
394 placeholderText.append( (*it).mText.GetText() );
397 return placeholderText ;
400 void TextInput::SetInitialText(const std::string& initialText)
402 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
404 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
406 mPreEditFlag = false;
407 mIgnoreCommitFlag = true;
410 SetText( initialText );
411 PreEditReset( false ); // Reset keyboard as text changed
414 void TextInput::SetText(const std::string& initialText)
416 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
418 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
420 if( mStyledText.empty() )
422 // If the initial text is empty, set the placeholder text.
423 mDisplayedTextView.SetText( mStyledPlaceHolderText );
424 mPlaceHolderSet = true;
428 mDisplayedTextView.SetText( mStyledText );
429 mPlaceHolderSet = false;
434 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
436 ImfManager imfManager = ImfManager::Get();
439 imfManager.SetCursorPosition( mCursorPosition );
440 imfManager.SetSurroundingText( initialText );
441 imfManager.NotifyCursorPosition();
444 if( IsScrollEnabled() )
446 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
449 ShowGrabHandleAndSetVisibility( false );
458 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
460 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
462 mDisplayedTextView.SetText( styleText );
463 mPlaceHolderSet = false;
465 // If text alignment hasn't been manually set by application developer, then we
466 // automatically determine the alignment based on the content of the text i.e. what
467 // language the text begins with.
468 // TODO: This should determine different alignments for each line (broken by '\n') of text.
469 if(!mOverrideAutomaticAlignment)
471 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
472 bool leftToRight(true);
474 if( !styleText.empty() )
476 bool breakOut(false);
478 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
480 const Text& text = textIter->mText;
482 for( std::size_t i = 0; i < text.GetLength(); ++i )
484 Character character( text[i] );
485 if( character.GetCharacterDirection() != Character::Neutral )
487 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
495 // Based on this direction, either left or right align text if not manually set by application developer.
496 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
497 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
498 Toolkit::Alignment::VerticalTop ) );
499 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
505 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
507 mMaxStringLength = maxChars;
510 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
512 DALI_ASSERT_DEBUG( maxLines > 0 )
516 mNumberOflinesLimit = maxLines;
520 std::size_t TextInput::GetNumberOfLinesLimit() const
522 return mNumberOflinesLimit;
525 std::size_t TextInput::GetNumberOfCharacters() const
527 return mStyledText.size();
531 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
533 mMaterialColor = color;
534 if ( mCustomMaterial )
536 mCustomMaterial.SetDiffuseColor( mMaterialColor );
537 mMeshData.SetMaterial( mCustomMaterial );
541 const Vector4& TextInput::GetMaterialDiffuseColor() const
543 return mMaterialColor;
548 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
550 return mInputStartedSignalV2;
553 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
555 return mInputFinishedSignalV2;
558 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
560 return mCutAndPasteToolBarDisplayedV2;
563 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
565 return mStyleChangedSignalV2;
568 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
570 return mTextModifiedSignal;
573 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
575 return mMaxInputCharactersReachedSignalV2;
578 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
580 return mInputTextExceedBoundariesSignalV2;
583 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
585 Dali::BaseHandle handle( object );
587 bool connected( true );
588 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
590 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
592 textInput.InputStartedSignal().Connect( tracker, functor );
594 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
596 textInput.InputFinishedSignal().Connect( tracker, functor );
598 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
600 textInput.StyleChangedSignal().Connect( tracker, functor );
602 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
604 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
606 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
608 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
612 // signalName does not match any signal
619 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
623 // update line height before calculate the actual position.
628 if( setCursorOnTouchPoint )
630 // Sets the cursor position for the given touch point.
631 ReturnClosestIndex( touchPoint, mCursorPosition );
633 // Creates the grab handle.
634 if( IsGrabHandleEnabled() )
636 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
640 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
641 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
642 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
643 ShowGrabHandleAndSetVisibility( true );
645 // Scrolls the text-view if needed.
646 if( IsScrollEnabled() )
648 ScrollTextViewToMakeCursorVisible( cursorPosition );
654 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
666 bool TextInput::IsEditable() const
668 return mEditModeActive;
671 void TextInput::SetEditOnTouch( bool editOnTouch )
673 mEditOnTouch = editOnTouch;
676 bool TextInput::IsEditOnTouch() const
681 void TextInput::SetTextSelectable( bool textSelectable )
683 mTextSelection = textSelectable;
686 bool TextInput::IsTextSelectable() const
688 return mTextSelection;
691 bool TextInput::IsTextSelected() const
693 return mHighlightMeshActor;
696 void TextInput::DeSelectText()
703 void TextInput::SetGrabHandleImage(Dali::Image image )
707 CreateGrabHandle(image);
711 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
713 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
717 mCursor.SetImage( image );
718 mCursor.SetNinePatchBorder( border );
722 Vector3 TextInput::GetSelectionHandleSize()
724 return DEFAULT_SELECTION_HANDLE_SIZE;
727 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
729 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
733 mCursorRTL.SetImage( image);
734 mCursorRTL.SetNinePatchBorder( border );
738 void TextInput::EnableGrabHandle(bool toggle)
740 // enables grab handle with will in turn de-activate magnifier
741 mGrabHandleEnabled = toggle;
744 bool TextInput::IsGrabHandleEnabled()
746 // if false then magnifier will be shown instead.
747 return mGrabHandleEnabled;
750 void TextInput::EnableSelectionHandleFlip( bool toggle )
752 // Deprecated function. To be removed.
753 mIsSelectionHandleFlipEnabled = toggle;
756 bool TextInput::IsSelectionHandleFlipEnabled()
758 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
762 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
764 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
765 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
766 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
768 mSelectionHandleFlipMargin = margin;
771 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
773 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
774 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
776 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
777 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
779 const Vector4 boundary( originX,
781 originX + boundingRectangle.width,
782 originY + boundingRectangle.height );
784 mBoundingRectangleWorldCoordinates = boundary;
787 const Rect<float> TextInput::GetBoundingRectangle() const
789 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
791 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
792 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
794 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
799 const Vector4& TextInput::GetSelectionHandleFlipMargin()
801 return mSelectionHandleFlipMargin;
804 void TextInput::SetTextColor( const Vector4& color )
806 mDisplayedTextView.SetColor( color );
809 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
811 if( style != mInputStyle )
814 bool emitSignal = false;
816 // mask: modify style according to mask, if different emit signal.
817 const TextStyle oldInputStyle( mInputStyle );
819 // Copy the new style.
820 mInputStyle.Copy( style, mask );
822 // if style has changed, emit signal.
823 if( oldInputStyle != mInputStyle )
828 // Updates the line height accordingly with the input style.
831 // Changing font point size will require the cursor to be re-sized
836 EmitStyleChangedSignal();
841 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
843 if ( IsTextSelected() )
845 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
846 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
848 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
850 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
853 // Keeps the old style to be compared with the new one.
854 const TextStyle oldInputStyle( mInputStyle );
856 // Copy only those parameters from the style which are set in the mask.
857 mInputStyle.Copy( style, mask );
859 if( mInputStyle != oldInputStyle )
861 // Updates the line height accordingly with the input style.
864 EmitStyleChangedSignal();
869 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
871 if( !mStyledText.empty() )
873 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
877 TextStyle TextInput::GetStyleAtCursor() const
881 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
883 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
884 style = mStyledText.at( mCursorPosition-1 ).mStyle;
890 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
892 Dali::Font defaultFont = Dali::Font::New();
893 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
900 TextStyle TextInput::GetStyleAt( std::size_t position ) const
902 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
904 if( position >= mStyledText.size() )
906 position = mStyledText.size() - 1;
909 return mStyledText.at( position ).mStyle;
912 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
914 mDisplayedTextView.SetTextAlignment( align );
915 mOverrideAutomaticAlignment = true;
918 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
920 mDisplayedTextView.SetLineJustification( justification );
921 mOverrideAutomaticAlignment = true;
924 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
926 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
929 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
931 return mDisplayedTextView.GetFadeBoundary();
934 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
936 return mDisplayedTextView.GetTextAlignment();
939 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
941 mDisplayedTextView.SetMultilinePolicy( policy );
944 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
946 return mDisplayedTextView.GetMultilinePolicy();
949 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
951 mDisplayedTextView.SetWidthExceedPolicy( policy );
954 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
956 return mDisplayedTextView.GetWidthExceedPolicy();
959 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
961 mDisplayedTextView.SetHeightExceedPolicy( policy );
964 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
966 return mDisplayedTextView.GetHeightExceedPolicy();
969 void TextInput::SetExceedEnabled( bool enable )
971 mExceedEnabled = enable;
974 bool TextInput::GetExceedEnabled() const
976 return mExceedEnabled;
979 void TextInput::SetBackground(Dali::Image image )
981 // TODO Should add this function and add public api to match.
984 bool TextInput::OnTouchEvent(const TouchEvent& event)
989 bool TextInput::OnKeyEvent(const KeyEvent& event)
991 switch( event.state )
995 return OnKeyDownEvent(event);
1001 return OnKeyUpEvent(event);
1013 void TextInput::OnKeyInputFocusGained()
1015 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1017 mEditModeActive = true;
1019 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1021 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1023 // Updates the line height accordingly with the input style.
1026 // Connect the signals to use in text input.
1027 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1028 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1030 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1033 GetTextLayoutInfo();
1036 SetCursorVisibility( true );
1037 StartCursorBlinkTimer();
1039 Toolkit::TextInput handle( GetOwner() );
1040 mInputStartedSignalV2.Emit( handle );
1042 ImfManager imfManager = ImfManager::Get();
1046 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1048 // Notify that the text editing start.
1049 imfManager.Activate();
1051 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1052 imfManager.SetRestoreAferFocusLost( true );
1054 imfManager.SetCursorPosition( mCursorPosition );
1055 imfManager.NotifyCursorPosition();
1058 mClipboard = Clipboard::Get(); // Store handle to clipboard
1060 // Now in edit mode we can accept string to paste from clipboard
1061 if( Adaptor::IsAvailable() )
1063 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1066 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1071 void TextInput::OnKeyInputFocusLost()
1073 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1077 // If key input focus is lost, it removes the
1078 // underline from the last pre-edit text.
1079 RemovePreEditStyle();
1080 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1081 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1085 ImfManager imfManager = ImfManager::Get();
1088 // The text editing is finished. Therefore the imf manager don't have restore activation.
1089 imfManager.SetRestoreAferFocusLost( false );
1091 // Notify that the text editing finish.
1092 imfManager.Deactivate();
1094 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1096 // Disconnect signal used the text input.
1097 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1099 Toolkit::TextInput handle( GetOwner() );
1100 mInputFinishedSignalV2.Emit( handle );
1101 mEditModeActive = false;
1102 mPreEditFlag = false;
1104 SetCursorVisibility( false );
1105 StopCursorBlinkTimer();
1107 ShowGrabHandleAndSetVisibility( false );
1110 // No longer in edit mode so do not want to receive string from clipboard
1111 if( Adaptor::IsAvailable() )
1113 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1116 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1118 Clipboard clipboard = Clipboard::Get();
1122 clipboard.HideClipboard();
1127 void TextInput::OnControlStageConnection()
1129 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1131 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1133 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1137 void TextInput::CreateActiveLayer()
1139 Actor self = Self();
1140 mActiveLayer = Layer::New();
1142 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1143 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1144 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1146 self.Add( mActiveLayer );
1147 mActiveLayer.RaiseToTop();
1150 void TextInput::OnInitialize()
1152 CreateTextViewActor();
1156 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1157 // different positions depending on language)
1158 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1159 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1160 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1162 Actor self = Self();
1163 self.Add( mCursor );
1164 self.Add( mCursorRTL );
1166 mCursorVisibility = false;
1168 CreateActiveLayer(); // todo move this so layer only created when needed.
1170 // Assign names to image actors
1171 mCursor.SetName("mainCursor");
1172 mCursorRTL.SetName("rtlCursor");
1175 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1177 mDisplayedTextView.SetSize( targetSize );
1178 GetTextLayoutInfo();
1179 mActiveLayer.SetSize(targetSize);
1182 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1184 Relayout( mDisplayedTextView, size, container );
1185 GetTextLayoutInfo();
1190 Vector3 TextInput::GetNaturalSize()
1192 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1194 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1196 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1197 naturalSize.height = mLineHeight;
1203 float TextInput::GetHeightForWidth( float width )
1205 float height = mDisplayedTextView.GetHeightForWidth( width );
1207 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1209 // If the height is zero, it means there is no text. Let's return the cursor height.
1210 height = mLineHeight;
1216 /*end of Virtual methods from parent*/
1218 // Private Internal methods
1220 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1222 switch (gesture.state)
1224 case Gesture::Started:
1225 // fall through so code not duplicated
1226 case Gesture::Continuing:
1228 if (actor == mGrabArea)
1230 SetCursorVisibility( true );
1231 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1232 MoveGrabHandle( gesture.displacement );
1233 HidePopup(); // Do not show popup whilst handle is moving
1235 else if (actor == mHandleOneGrabArea)
1237 // the displacement in PanGesture is affected by the actor's rotation.
1238 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1239 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1241 MoveSelectionHandle( HandleOne, gesture.displacement );
1243 mState = StateDraggingHandle;
1246 else if (actor == mHandleTwoGrabArea)
1248 // the displacement in PanGesture is affected by the actor's rotation.
1249 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1250 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1252 MoveSelectionHandle( HandleTwo, gesture.displacement );
1254 mState = StateDraggingHandle;
1260 case Gesture::Finished:
1262 // Revert back to non-pressed selection handle images
1263 if (actor == mGrabArea)
1265 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1266 SetCursorVisibility( true );
1267 SetUpPopUpSelection();
1270 if (actor == mHandleOneGrabArea)
1272 // the displacement in PanGesture is affected by the actor's rotation.
1273 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1274 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1276 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1278 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1280 ShowPopupCutCopyPaste();
1282 if (actor == mHandleTwoGrabArea)
1284 // the displacement in PanGesture is affected by the actor's rotation.
1285 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1286 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1288 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1290 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1292 ShowPopupCutCopyPaste();
1301 // Stop the flashing animation so easy to see when moved.
1302 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1304 if (touch.GetPoint(0).state == TouchPoint::Down)
1306 SetCursorVisibility( true );
1307 StopCursorBlinkTimer();
1309 else if (touch.GetPoint(0).state == TouchPoint::Up)
1311 SetCursorVisibility( true );
1312 StartCursorBlinkTimer();
1317 // selection handle one
1318 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1320 if (touch.GetPoint(0).state == TouchPoint::Down)
1322 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1324 else if (touch.GetPoint(0).state == TouchPoint::Up)
1326 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1331 // selection handle two
1332 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1334 if (touch.GetPoint(0).state == TouchPoint::Down)
1336 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1338 else if (touch.GetPoint(0).state == TouchPoint::Up)
1340 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1345 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1347 // If text exists then select nearest word.
1348 if ( !mStyledText.empty())
1352 ShowGrabHandleAndSetVisibility( false );
1357 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1358 // converts the pre-edit word being displayed to a committed word.
1359 if ( !mUnderlinedPriorToPreEdit )
1362 style.SetUnderline( false );
1363 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1365 mPreEditFlag = false;
1366 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1367 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1368 PreEditReset( false );
1370 mCursorPosition = 0;
1372 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1373 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1375 ImfManager imfManager = ImfManager::Get();
1378 imfManager.SetCursorPosition ( mCursorPosition );
1379 imfManager.NotifyCursorPosition();
1382 std::size_t start = 0;
1383 std::size_t end = 0;
1384 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1386 SelectText( start, end );
1388 // if no text but clipboard has content then show paste option
1389 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1391 ShowPopupCutCopyPaste();
1394 // If no text and clipboard empty then do nothing
1397 // TODO: Change the function name to be more general.
1398 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1400 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1401 , (mEditOnTouch)?"true":"false"
1402 , (mEditModeActive)?"true":"false");
1404 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1409 if( mGrabArea == actor )
1411 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1413 SetUpPopUpSelection();
1423 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1425 // Initially don't create the grab handle.
1426 bool createGrabHandle = false;
1428 if ( !mEditModeActive )
1430 // update line height before calculate the actual position.
1433 // Only start edit mode if TextInput configured to edit on touch
1436 // Set the initial cursor position in the tap point.
1437 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1439 // Create the grab handle.
1440 // TODO Make this a re-usable function.
1441 if ( IsGrabHandleEnabled() )
1443 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1447 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1448 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1449 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1450 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1454 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1455 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1456 // otherwise the Grab handle will be shown when selecting.
1463 // Show the keyboard if it was hidden.
1464 if (!VirtualKeyboard::IsVisible())
1466 VirtualKeyboard::Show();
1469 // Reset keyboard as tap event has occurred.
1470 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1471 PreEditReset( true );
1473 GetTextLayoutInfo();
1475 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1477 // 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.
1479 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1481 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1483 // Notify keyboard so it can 're-capture' word for predictive text.
1484 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1485 ImfManager imfManager = ImfManager::Get();
1488 imfManager.SetCursorPosition ( mCursorPosition );
1489 imfManager.NotifyCursorPosition();
1491 const TextStyle oldInputStyle( mInputStyle );
1493 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1497 // Create the grab handle.
1498 // Grab handle is created later.
1499 createGrabHandle = true;
1501 if( oldInputStyle != mInputStyle )
1503 // Updates the line height accordingly with the input style.
1506 EmitStyleChangedSignal();
1511 if ( createGrabHandle && IsGrabHandleEnabled() )
1513 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1517 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1518 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1519 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1520 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1525 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1527 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1529 if(longPress.state == Dali::Gesture::Started)
1531 // Start edit mode on long press
1532 if ( !mEditModeActive )
1537 // If text exists then select nearest word.
1538 if ( !mStyledText.empty())
1542 ShowGrabHandleAndSetVisibility( false );
1547 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1548 // converts the pre-edit word being displayed to a committed word.
1549 if ( !mUnderlinedPriorToPreEdit )
1552 style.SetUnderline( false );
1553 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1555 mPreEditFlag = false;
1556 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1557 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1558 PreEditReset( false );
1560 mCursorPosition = 0;
1562 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1563 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1565 ImfManager imfManager = ImfManager::Get();
1568 imfManager.SetCursorPosition ( mCursorPosition );
1569 imfManager.NotifyCursorPosition();
1571 std::size_t start = 0;
1572 std::size_t end = 0;
1573 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1575 SelectText( start, end );
1578 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1579 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1581 ShowPopupCutCopyPaste();
1586 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1588 const Text clipboardText( notifier.GetContent() );
1589 PasteText( clipboardText );
1591 SetCursorVisibility( true );
1592 StartCursorBlinkTimer();
1594 ShowGrabHandleAndSetVisibility( false );
1600 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1602 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1604 const std::string& name = button.GetName();
1606 if(name == TextInputPopup::OPTION_SELECT_WORD)
1608 std::size_t start = 0;
1609 std::size_t end = 0;
1610 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1612 SelectText( start, end );
1614 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1616 SetCursorVisibility(false);
1617 StopCursorBlinkTimer();
1619 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1620 std::size_t start = 0;
1622 SelectText( start, end );
1624 else if(name == TextInputPopup::OPTION_CUT)
1626 bool ret = CopySelectedTextToClipboard();
1630 DeleteHighlightedText( true );
1634 SetCursorVisibility( true );
1635 StartCursorBlinkTimer();
1639 else if(name == TextInputPopup::OPTION_COPY)
1641 CopySelectedTextToClipboard();
1645 SetCursorVisibility( true );
1646 StartCursorBlinkTimer();
1650 else if(name == TextInputPopup::OPTION_PASTE)
1652 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1654 PasteText(retrievedString);
1656 SetCursorVisibility( true );
1657 StartCursorBlinkTimer();
1659 ShowGrabHandleAndSetVisibility( false );
1663 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1665 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1666 // Hence pass the false parameter for signalFinished.
1667 HidePopup( true, false );
1668 mClipboard.ShowClipboard();
1674 bool TextInput::OnCursorBlinkTimerTick()
1677 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1678 if ( mCursorRTLEnabled )
1680 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1682 mCursorBlinkStatus = !mCursorBlinkStatus;
1687 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1689 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1691 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1692 if(mHighlightMeshActor && mState == StateEdit)
1694 ShowPopupCutCopyPaste();
1698 //FIXME this routine needs to be re-written as it contains too many branches.
1699 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1701 std::string keyName = event.keyPressedName;
1702 std::string keyString = event.keyPressed;
1704 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1706 // Do not consume "Tab" and "Escape" keys.
1707 if(keyName == "Tab" || keyName == "Escape")
1709 // Escape key to end the edit mode
1715 HidePopup(); // If Pop-up shown then hides it as editing text.
1717 // Update Flag, indicates whether to update the text-input contents or not.
1718 // Any key stroke that results in a visual change of the text-input should
1719 // set this flag to true.
1722 // Whether to scroll text to cursor position.
1723 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1724 bool scroll = false;
1726 if (keyName == "Return")
1728 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1730 bool preEditFlagPreviouslySet( mPreEditFlag );
1732 if (mHighlightMeshActor)
1734 // replaces highlighted text with new line
1735 DeleteHighlightedText( false );
1737 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1739 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1740 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1743 mCommitByKeyInput = true;
1746 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1747 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1749 mPreEditFlag = true;
1750 mIgnoreCommitFlag = false;
1760 else if ( keyName == "space" )
1762 if ( mHighlightMeshActor )
1764 // Some text is selected so erase it before adding space.
1765 DeleteHighlightedText( true );
1769 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1771 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1772 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1775 mCommitByKeyInput = true;
1780 else if (keyName == "BackSpace")
1782 if ( mHighlightMeshActor )
1784 // Some text is selected so erase it
1785 DeleteHighlightedText( true );
1790 if ( mCursorPosition > 0 )
1792 DeleteCharacter( mCursorPosition );
1798 else if (keyName == "Right")
1803 else if (keyName == "Left")
1805 AdvanceCursor(true);
1808 else // event is a character
1810 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1811 if ( !keyString.empty() )
1813 if ( mHighlightMeshActor )
1815 // replaces highlighted text with new character
1816 DeleteHighlightedText( false );
1820 // Received key String
1821 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1827 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1828 // as this is a costly operation.
1834 if(update || scroll)
1836 if( IsScrollEnabled() )
1838 // Calculates the new cursor position (in actor coordinates)
1839 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1841 ScrollTextViewToMakeCursorVisible( cursorPosition );
1848 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1850 std::string keyName = event.keyPressedName;
1851 std::string keyString = event.keyPressed;
1853 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1855 // The selected text become deselected when the key code is DALI_KEY_BACK.
1856 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1865 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1867 // Updates the stored scroll position.
1868 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1870 const Vector3& controlSize = GetControlSize();
1871 Size cursorSize( CURSOR_THICKNESS, 0.f );
1873 // Updates the cursor and grab handle position and visibility.
1874 if( mGrabHandle || mCursor )
1876 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1877 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1879 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1881 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1885 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1886 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1891 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1892 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1896 // Updates the selection handles and highlighted text position and visibility.
1897 if( mSelectionHandleOne && mSelectionHandleTwo )
1899 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1900 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1901 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1902 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1903 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1904 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1906 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1907 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1909 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1910 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1911 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1912 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1914 if( mHighlightMeshActor )
1916 mHighlightMeshActor.SetVisible( true );
1922 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1924 // Scroll the text to make the cursor visible.
1925 const Size cursorSize( CURSOR_THICKNESS,
1926 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1928 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1930 const Vector3& controlSize = GetControlSize();
1932 // Calculates the new scroll position.
1933 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1934 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1936 scrollOffset.x += cursorPosition.x;
1939 if( cursorPosition.y - cursorSize.height < 0.f )
1941 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1943 else if( cursorPosition.y > controlSize.height )
1945 scrollOffset.y += cursorPosition.y;
1948 // Sets the new scroll position.
1949 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1950 SetScrollPosition( scrollOffset );
1953 void TextInput::StartScrollTimer()
1957 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1958 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1961 if( !mScrollTimer.IsRunning() )
1963 mScrollTimer.Start();
1967 void TextInput::StopScrollTimer()
1971 mScrollTimer.Stop();
1975 bool TextInput::OnScrollTimerTick()
1977 // TODO: need to set the new style accordingly the new handle position.
1979 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1981 // nothing to do if all handles are invisible or doesn't exist.
1987 // Choose between the grab handle or the selection handles.
1988 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1989 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1990 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1992 std::size_t newCursorPosition = 0;
1993 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1995 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1996 // the new selection handle's position needs to be different of the other one.
1997 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1998 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1999 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2001 if( differentSelectionHandles )
2003 handlePosition = newCursorPosition;
2005 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2007 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2009 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2010 scrollPosition += scrollDelta;
2011 SetScrollPosition( scrollPosition );
2013 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2018 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2021 actualHandlePosition.x += mScrollDisplacement.x;
2022 actualHandlePosition.y += mScrollDisplacement.y;
2027 // Public Internal Methods (public for testing purpose)
2029 void TextInput::SetUpTouchEvents()
2031 if ( !mTapDetector )
2033 mTapDetector = TapGestureDetector::New();
2034 // Attach the actors and connect the signal
2035 mTapDetector.Attach(Self());
2037 // As contains children which may register for tap the default control detector is not used.
2038 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2041 if ( !mDoubleTapDetector )
2043 mDoubleTapDetector = TapGestureDetector::New();
2044 mDoubleTapDetector.SetTapsRequired( 2 );
2045 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2047 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2048 // so that we do not, unnecessarily, have a double tap request all the time
2051 if ( !mPanGestureDetector )
2053 mPanGestureDetector = PanGestureDetector::New();
2054 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2057 if ( !mLongPressDetector )
2059 mLongPressDetector = LongPressGestureDetector::New();
2060 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2061 mLongPressDetector.Attach(Self());
2065 void TextInput::CreateTextViewActor()
2067 mDisplayedTextView = Toolkit::TextView::New();
2068 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2069 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2070 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2071 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2072 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2073 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2074 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2075 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2076 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2077 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2079 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2081 Self().Add( mDisplayedTextView );
2084 // Start a timer to initiate, used by the cursor to blink.
2085 void TextInput::StartCursorBlinkTimer()
2087 if ( !mCursorBlinkTimer )
2089 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2090 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2093 if ( !mCursorBlinkTimer.IsRunning() )
2095 mCursorBlinkTimer.Start();
2099 // Start a timer to initiate, used by the cursor to blink.
2100 void TextInput::StopCursorBlinkTimer()
2102 if ( mCursorBlinkTimer )
2104 mCursorBlinkTimer.Stop();
2108 void TextInput::StartEditMode()
2110 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2112 if(!mEditModeActive)
2117 if ( mDoubleTapDetector )
2119 mDoubleTapDetector.Attach( Self() );
2123 void TextInput::EndEditMode()
2125 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2127 ClearKeyInputFocus();
2129 if ( mDoubleTapDetector )
2131 mDoubleTapDetector.Detach( Self() );
2135 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2137 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2139 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2141 style.SetUnderline( true );
2142 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2146 void TextInput::RemovePreEditStyle()
2148 if ( !mUnderlinedPriorToPreEdit )
2151 style.SetUnderline( false );
2152 SetActiveStyle( style, TextStyle::UNDERLINE );
2156 // IMF related methods
2159 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2161 bool update( false );
2162 bool preeditResetRequired ( false );
2164 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2166 HidePopup(); // If Pop-up shown then hides it as editing text.
2169 switch ( imfEvent.eventName )
2171 case ImfManager::PREEDIT:
2173 mIgnoreFirstCommitFlag = false;
2175 // 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
2176 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2178 // replaces highlighted text with new character
2179 DeleteHighlightedText( false );
2182 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2184 if( IsScrollEnabled() )
2186 // Calculates the new cursor position (in actor coordinates)
2187 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2188 ScrollTextViewToMakeCursorVisible( cursorPosition );
2195 case ImfManager::COMMIT:
2197 if( mIgnoreFirstCommitFlag )
2199 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2200 mIgnoreFirstCommitFlag = false;
2204 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2206 // 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
2207 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2209 // replaces highlighted text with new character
2210 DeleteHighlightedText( false );
2213 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2214 // not needed, one such scenario is when the pre-edit word is too long to fit.
2215 if ( !mIgnoreCommitFlag )
2217 update = CommitReceived( imfEvent.predictiveString );
2221 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2227 if( IsScrollEnabled() )
2229 // Calculates the new cursor position (in actor coordinates)
2230 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2232 ScrollTextViewToMakeCursorVisible( cursorPosition );
2237 case ImfManager::DELETESURROUNDING:
2239 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2240 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2242 mPreEditFlag = false;
2244 std::size_t toDelete = 0;
2245 std::size_t numberOfCharacters = 0;
2247 if( mHighlightMeshActor )
2249 // delete highlighted text.
2250 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2251 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2255 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2257 toDelete = mCursorPosition + imfEvent.cursorOffset;
2259 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2261 numberOfCharacters = mStyledText.size() - toDelete;
2265 numberOfCharacters = imfEvent.numberOfChars;
2268 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2269 DeleteRange( toDelete, numberOfCharacters );
2271 mCursorPosition = toDelete;
2272 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2276 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2279 case ImfManager::GETSURROUNDING:
2281 // 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
2282 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2283 if (! ( mHighlightMeshActor || mSelectingText ) )
2285 std::string text( GetText() );
2286 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2288 imfManager.SetCursorPosition( mCursorPosition );
2289 imfManager.SetSurroundingText( text );
2292 if( 0 != mNumberOfSurroundingCharactersDeleted )
2294 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2295 mNumberOfSurroundingCharactersDeleted = 0;
2297 if( mStyledText.empty() )
2299 // Styled text is empty, so set the placeholder text.
2300 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2301 mPlaceHolderSet = true;
2306 case ImfManager::VOID:
2308 DALI_ASSERT_DEBUG( false );
2312 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2314 return callbackData;
2317 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2319 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2321 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2322 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2324 bool preeditResetRequest ( false );
2326 if( mPreEditFlag ) // Already in pre-edit state.
2328 if( mStyledText.size() >= mMaxStringLength )
2330 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2331 // Cannot fit these characters into field, clear pre-edit.
2332 if ( !mUnderlinedPriorToPreEdit )
2335 style.SetUnderline( false );
2336 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2338 mIgnoreCommitFlag = true;
2339 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2340 mPreEditFlag = false;
2341 EmitMaxInputCharactersReachedSignal();
2345 // delete existing pre-edit string
2346 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2348 // Store new pre-edit string
2349 mPreEditString.SetText( keyString );
2351 if ( keyString.empty() )
2353 mPreEditFlag = false;
2354 mCursorPosition = mPreEditStartPosition;
2356 if( mStyledText.empty() )
2358 // Styled text is empty, so set the placeholder text.
2359 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2360 mPlaceHolderSet = true;
2364 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2366 GetTextLayoutInfo();
2371 // Insert new pre-edit string. InsertAt updates the size and position table.
2372 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2373 // 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.
2374 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2375 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2376 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2379 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2383 else // mPreEditFlag not set
2385 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2387 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2388 // new pre-edit so move into pre-edit state by setting flag
2389 mPreEditFlag = true;
2390 mPreEditString.SetText( keyString ); // store new pre-edit string
2391 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2392 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2393 // 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.
2394 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2395 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2396 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2397 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2403 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2407 return preeditResetRequest;
2410 bool TextInput::CommitReceived(const std::string& keyString )
2412 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2413 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2415 bool update( false );
2417 RemovePreEditStyle();
2419 const std::size_t styledTextSize( mStyledText.size() );
2420 if( styledTextSize >= mMaxStringLength )
2422 // Cannot fit these characters into field, clear pre-edit.
2425 mIgnoreCommitFlag = true;
2426 mPreEditFlag = false;
2428 EmitMaxInputCharactersReachedSignal();
2434 // delete existing pre-edit string
2435 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2436 mPreEditFlag = false;
2438 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2439 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2441 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2443 // No need to update cursor position as Cursor location given by touch.
2444 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2445 mPreserveCursorPosition = false;
2449 // Cursor not set by touch so needs to be re-positioned to input more text
2450 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2452 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2453 if ( mCommitByKeyInput )
2455 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2456 mCommitByKeyInput = false;
2462 if ( mSelectTextOnCommit )
2464 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2469 else // mPreEditFlag not set
2471 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2473 if( mStyledText.empty() && mPlaceHolderSet )
2475 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2476 mDisplayedTextView.SetText( "" );
2477 mNumberOfSurroundingCharactersDeleted = 0;
2478 mPlaceHolderSet = false;
2480 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2482 mNumberOfSurroundingCharactersDeleted = 0;
2487 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2492 mSelectTextOnCommit = false;
2494 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2495 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2500 // End of IMF related methods
2502 std::size_t TextInput::DeletePreEdit()
2504 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2506 DALI_ASSERT_DEBUG( mPreEditFlag );
2508 const std::size_t preEditStringLength = mPreEditString.GetLength();
2509 const std::size_t styledTextSize = mStyledText.size();
2511 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2513 // Prevents erase items outside mStyledText bounds.
2514 if( mPreEditStartPosition > styledTextSize )
2516 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2517 mPreEditStartPosition = styledTextSize;
2520 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2522 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2523 endPosition = styledTextSize;
2526 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2528 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2529 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2531 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2533 return preEditStringLength;
2536 void TextInput::PreEditReset( bool preserveCursorPosition )
2538 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2539 preserveCursorPosition, mCursorPosition);
2541 // 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.
2542 mPreserveCursorPosition = preserveCursorPosition;
2544 // Reset incase we are in a pre-edit state.
2545 ImfManager imfManager = ImfManager::Get();
2548 imfManager.Reset(); // Will trigger a commit message
2552 void TextInput::CursorUpdate()
2556 ImfManager imfManager = ImfManager::Get();
2559 std::string text( GetText() );
2560 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2561 imfManager.SetCursorPosition ( mCursorPosition );
2562 imfManager.NotifyCursorPosition();
2566 /* Delete highlighted characters redisplay*/
2567 void TextInput::DeleteHighlightedText( bool inheritStyle )
2569 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2571 if(mHighlightMeshActor)
2573 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2575 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2576 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2578 // Get the styled text of the characters to be deleted as it may be needed if
2579 // the "exceed the text-input's boundaries" option is disabled.
2580 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2582 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2584 mStyledText.erase( start, end ); // erase range of characters
2586 // Remove text from TextView.
2588 if( mStyledText.empty() )
2590 // Styled text is empty, so set the placeholder text.
2591 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2592 mPlaceHolderSet = true;
2596 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2598 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2600 // It may happen than after removing a white space or a new line character,
2601 // two words merge, this new word could be big enough to not fit in its
2602 // current line, so moved to the next one, and make some part of the text to
2603 // exceed the text-input's boundary.
2604 if( !mExceedEnabled )
2606 // Get the new text layout after removing some characters.
2607 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2609 // Get text-input's size.
2610 const Vector3& size = GetControlSize();
2612 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2613 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2615 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2617 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2618 styledCharactersToDelete.begin(),
2619 styledCharactersToDelete.end() );
2623 GetTextLayoutInfo();
2629 const TextStyle oldInputStyle( mInputStyle );
2631 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2633 if( oldInputStyle != mInputStyle )
2635 // Updates the line height accordingly with the input style.
2638 EmitStyleChangedSignal();
2644 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2646 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2647 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2649 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2652 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2654 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2655 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2657 mStyledText.erase(itStart, itEnd);
2659 // update the selection handles if they are visible.
2660 if( mHighlightMeshActor )
2662 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2663 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2665 if( minHandle >= start + ncharacters )
2667 minHandle -= ncharacters;
2669 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2674 if( maxHandle >= start + ncharacters )
2676 maxHandle -= ncharacters;
2678 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2684 // 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.
2687 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2689 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2690 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2691 // Mean we do not re-draw the text more than we have too.
2694 /* Delete character at current cursor position and redisplay*/
2695 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2697 // Ensure positionToDelete is not out of bounds.
2698 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2699 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2700 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2702 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2705 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2707 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2709 // Get the styled text of the character to be deleted as it may be needed if
2710 // the "exceed the text-input's boundaries" option is disabled.
2711 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2713 mStyledText.erase(it); // erase the character left of positionToDelete
2715 if( mStyledText.empty() )
2717 // Styled text is empty, so set the placeholder text.
2718 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2719 mPlaceHolderSet = true;
2723 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2725 const Character characterToDelete = styledCharacterToDelete.mText[0];
2727 // It may happen than after removing a white space or a new line character,
2728 // two words merge, this new word could be big enough to not fit in its
2729 // current line, so moved to the next one, and make some part of the text to
2730 // exceed the text-input's boundary.
2731 if( !mExceedEnabled )
2733 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2735 // Get the new text layout after removing one character.
2736 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2738 // Get text-input's size.
2739 const Vector3& size = GetControlSize();
2741 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2742 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2744 MarkupProcessor::StyledTextArray array;
2745 array.push_back( styledCharacterToDelete );
2746 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2748 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2753 GetTextLayoutInfo();
2755 ShowGrabHandleAndSetVisibility( false );
2757 mCursorPosition = positionToDelete -1;
2759 const TextStyle oldInputStyle( mInputStyle );
2761 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2763 if( oldInputStyle != mInputStyle )
2765 // Updates the line height accordingly with the input style.
2768 EmitStyleChangedSignal();
2773 /*Insert new character into the string and (optionally) redisplay text-input*/
2774 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2776 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2778 // Ensure insertionPosition is not out of bounds.
2779 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2781 bool textExceedsMaximunNumberOfCharacters = false;
2782 bool textExceedsBoundary = false;
2783 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2785 ShowGrabHandleAndSetVisibility( false );
2787 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2791 mIgnoreCommitFlag = true;
2792 mPreEditFlag = false;
2793 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2794 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2797 if( textExceedsMaximunNumberOfCharacters )
2799 EmitMaxInputCharactersReachedSignal();
2802 if( textExceedsBoundary )
2804 EmitInputTextExceedsBoundariesSignal();
2805 PreEditReset( false );
2809 return insertedStringLength;
2812 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2818 cursor = ImageActor::New( cursorImage );
2822 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2825 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2826 cursor.SetNinePatchBorder( border );
2828 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2829 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2830 cursor.SetVisible(false);
2835 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2837 // As cursor is not moving due to grab handle, handle should be hidden.
2838 ShowGrabHandleAndSetVisibility( false );
2840 bool cursorPositionChanged = false;
2843 if ( mCursorPosition >= places )
2845 mCursorPosition = mCursorPosition - places;
2846 cursorPositionChanged = true;
2851 if ((mCursorPosition + places) <= mStyledText.size())
2853 mCursorPosition = mCursorPosition + places;
2854 cursorPositionChanged = true;
2858 if( cursorPositionChanged )
2860 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2862 const TextStyle oldInputStyle( mInputStyle );
2863 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2867 if( oldInputStyle != mInputStyle )
2869 // Updates the line height accordingly with the input style.
2872 EmitStyleChangedSignal();
2875 ImfManager imfManager = ImfManager::Get();
2878 imfManager.SetCursorPosition ( mCursorPosition );
2879 imfManager.NotifyCursorPosition();
2884 void TextInput::DrawCursor(const std::size_t nthChar)
2886 // Get height of cursor and set its size
2887 Size size( CURSOR_THICKNESS, 0.0f );
2888 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2890 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2894 // Measure Font so know how big text will be if no initial text to measure.
2895 size.height = mLineHeight;
2898 mCursor.SetSize(size);
2900 // If the character is italic then the cursor also tilts.
2901 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2903 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2905 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2907 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2908 bool altPositionValid; // Alternate cursor validity flag.
2909 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2910 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2912 SetAltCursorEnabled( altPositionValid );
2914 if(!altPositionValid)
2916 mCursor.SetPosition( position + UI_OFFSET );
2920 size.height *= 0.5f;
2921 mCursor.SetSize(size);
2922 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2924 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2925 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2926 size.height = rowSize.height * 0.5f;
2927 mCursorRTL.SetSize(size);
2928 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2931 if( IsScrollEnabled() )
2933 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2934 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2939 void TextInput::SetAltCursorEnabled( bool enabled )
2941 mCursorRTLEnabled = enabled;
2942 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2945 void TextInput::SetCursorVisibility( bool visible )
2947 mCursorVisibility = visible;
2948 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2949 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2952 void TextInput::CreateGrabHandle( Dali::Image image )
2958 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2962 mGrabHandleImage = image;
2965 mGrabHandle = ImageActor::New(mGrabHandleImage);
2966 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2967 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2969 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2971 ShowGrabHandleAndSetVisibility( false );
2973 CreateGrabArea( mGrabHandle );
2975 mActiveLayer.Add(mGrabHandle);
2979 void TextInput::CreateGrabArea( Actor& parent )
2981 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2982 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2983 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2984 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2985 mTapDetector.Attach( mGrabArea );
2986 mPanGestureDetector.Attach( mGrabArea );
2988 parent.Add(mGrabArea);
2991 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2993 Vector3 actualHandlePosition;
2997 mActualGrabHandlePosition.x += displacement.x;
2998 mActualGrabHandlePosition.y += displacement.y;
3000 // Grab handle should jump to the nearest character and take cursor with it
3001 std::size_t newCursorPosition = 0;
3002 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3004 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
3006 bool handleVisible = true;
3008 if( IsScrollEnabled() )
3010 const Vector3 controlSize = GetControlSize();
3011 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
3012 // Scrolls the text if the handle is not in a visible position
3013 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3020 mCurrentHandlePosition = actualHandlePosition;
3021 mScrollDisplacement = Vector2::ZERO;
3025 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3027 mScrollDisplacement.x = -SCROLL_SPEED;
3029 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3031 mScrollDisplacement.x = SCROLL_SPEED;
3033 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3035 mScrollDisplacement.y = -SCROLL_SPEED;
3037 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3039 mScrollDisplacement.y = SCROLL_SPEED;
3045 if( handleVisible && // Only redraw cursor and do updates if position changed
3046 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3048 mCursorPosition = newCursorPosition;
3050 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3052 const TextStyle oldInputStyle( mInputStyle );
3054 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3056 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3058 if( oldInputStyle != mInputStyle )
3060 // Updates the line height accordingly with the input style.
3063 EmitStyleChangedSignal();
3068 return actualHandlePosition;
3071 void TextInput::ShowGrabHandle( bool visible )
3073 if ( IsGrabHandleEnabled() )
3077 mGrabHandle.SetVisible( mGrabHandleVisibility );
3079 StartMonitoringStageForTouch();
3083 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3085 mGrabHandleVisibility = visible;
3086 ShowGrabHandle( visible );
3089 // Callbacks connected to be Property notifications for Boundary checking.
3091 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3093 mIsSelectionHandleOneFlipped = true;
3094 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3095 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3098 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3100 mIsSelectionHandleOneFlipped = false;
3101 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3102 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3105 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3107 mIsSelectionHandleTwoFlipped = true;
3108 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3109 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3112 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3114 mIsSelectionHandleTwoFlipped = false;
3115 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3116 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3119 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3120 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3122 mSelectionHandleOne.SetOpacity(0.0f);
3125 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3127 mSelectionHandleOne.SetOpacity(1.0f);
3130 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3132 mSelectionHandleTwo.SetOpacity(0.0f);
3135 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3137 mSelectionHandleTwo.SetOpacity(1.0f);
3140 // End of Callbacks connected to be Property notifications for Boundary checking.
3142 void TextInput::SetUpHandlePropertyNotifications()
3144 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3146 Vector3 handlesize = GetSelectionHandleSize();
3148 // Exceeding horizontal boundary
3149 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3150 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3152 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3153 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3155 // Within horizontal boundary
3156 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3157 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3159 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3160 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3162 // Exceeding vertical boundary
3163 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3164 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3165 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3166 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3168 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3169 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3170 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3171 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3173 // Within vertical boundary
3174 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3175 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3176 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3177 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3179 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3180 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3181 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3182 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3185 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3187 mSelectionHandleOnePosition = start;
3188 mSelectionHandleTwoPosition = end;
3190 if ( !mSelectionHandleOne )
3192 // create normal and pressed images
3193 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3194 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3196 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3197 mSelectionHandleOne.SetName("SelectionHandleOne");
3198 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3199 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3200 mIsSelectionHandleOneFlipped = false;
3201 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3203 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3204 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3206 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3207 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3209 mTapDetector.Attach( mHandleOneGrabArea );
3210 mPanGestureDetector.Attach( mHandleOneGrabArea );
3212 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3214 mSelectionHandleOne.Add( mHandleOneGrabArea );
3215 mActiveLayer.Add( mSelectionHandleOne );
3218 if ( !mSelectionHandleTwo )
3220 // create normal and pressed images
3221 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3222 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3224 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3225 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3226 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3227 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3228 mIsSelectionHandleTwoFlipped = false;
3229 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3231 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3232 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3233 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3234 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3236 mTapDetector.Attach( mHandleTwoGrabArea );
3237 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3239 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3241 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3243 mActiveLayer.Add( mSelectionHandleTwo );
3246 SetUpHandlePropertyNotifications();
3248 // update table as text may have changed.
3249 GetTextLayoutInfo();
3251 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3252 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3254 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3255 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3257 // Calculates and set the visibility if the scroll mode is enabled.
3258 bool isSelectionHandleOneVisible = true;
3259 bool isSelectionHandleTwoVisible = true;
3260 if( IsScrollEnabled() )
3262 const Vector3& controlSize( GetControlSize() );
3263 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3264 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3265 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3266 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3269 CreateHighlight(); // function will only create highlight if not already created.
3272 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3274 Vector3 actualHandlePosition;
3276 if ( mSelectionHandleOne && mSelectionHandleTwo )
3278 const Vector3& controlSize = GetControlSize();
3280 Size cursorSize( CURSOR_THICKNESS, 0.f );
3282 // Get a reference of the wanted selection handle (handle one or two).
3283 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3285 // Get a reference for the current position of the handle and a copy of its pair
3286 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3287 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3289 // Get a handle of the selection handle actor
3290 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3292 // Selection handles should jump to the nearest character
3293 std::size_t newHandlePosition = 0;
3294 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3296 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3298 bool handleVisible = true;
3300 if( IsScrollEnabled() )
3302 mCurrentSelectionId = handleId;
3304 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3305 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3306 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3313 mCurrentSelectionHandlePosition = actualHandlePosition;
3314 mScrollDisplacement = Vector2::ZERO;
3318 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3320 mScrollDisplacement.x = -SCROLL_SPEED;
3322 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3324 mScrollDisplacement.x = SCROLL_SPEED;
3326 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3328 mScrollDisplacement.y = -SCROLL_SPEED;
3330 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3332 mScrollDisplacement.y = SCROLL_SPEED;
3338 if ( handleVisible && // Ensure the handle is visible.
3339 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3340 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3342 currentSelectionHandlePosition = newHandlePosition;
3344 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3345 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3349 if ( handleId == HandleOne )
3351 const TextStyle oldInputStyle( mInputStyle );
3353 // Set Active Style to that of first character in selection
3354 if( mSelectionHandleOnePosition < mStyledText.size() )
3356 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3359 if( oldInputStyle != mInputStyle )
3361 // Updates the line height accordingly with the input style.
3364 EmitStyleChangedSignal();
3370 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3373 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3376 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3377 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3379 if ( selectionHandleActor )
3381 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3382 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3383 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3385 if( IsScrollEnabled() )
3387 const Size cursorSize( CURSOR_THICKNESS,
3388 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3389 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3391 GetControlSize() ) );
3396 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3398 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3399 // For now the visual position of end of logical string will be end of visual string.
3400 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3402 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3405 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3407 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3408 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3409 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3410 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3412 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3414 // Deselect text prior to startSelectionIt
3415 for(;it!=startSelectionIt;++it)
3417 selectedVisualText[*it] = false;
3420 // Select text from startSelectionIt -> endSelectionIt
3421 for(;it!=endSelectionIt;++it)
3423 selectedVisualText[*it] = true;
3426 // Deselect text after endSelection
3429 selectedVisualText[*it] = false;
3432 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3435 // Calculate the dimensions of the quads they will make the highlight mesh
3436 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3438 // At the moment there is no public API to modify the block alignment option.
3439 const bool blockAlignEnabled = true;
3441 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3443 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3445 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3446 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3448 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3449 std::vector<bool> selectedVisualText;
3450 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3451 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3452 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3454 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3455 float rowLeft = 0.0f;
3456 float rowRight = 0.0f;
3457 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3458 float maxRowLeft = std::numeric_limits<float>::max();
3459 float maxRowRight = 0.0f;
3461 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3463 // Scan through entire text.
3466 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3468 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3469 bool charSelected( false );
3470 if( selectedIt != selectedEndIt )
3472 charSelected = *selectedIt++;
3475 if(selectionState == SelectionNone)
3479 selectionState = SelectionStarted;
3480 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3481 rowRight = rowLeft + charInfo.mSize.width;
3484 else if(selectionState == SelectionStarted)
3486 // break selection on:
3487 // 1. new line causing selection break. (\n or wordwrap)
3488 // 2. character not selected.
3489 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3492 // finished selection.
3493 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3494 // that it resides on. That way this enumeration is not necessary.
3496 if(lastIt->mIsNewLineChar)
3498 // 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.
3499 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3501 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3502 maxRowLeft = std::min(maxRowLeft, min.x);
3503 maxRowRight = std::max(maxRowRight, max.x);
3504 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3505 float rowTop = rowBottom - rowSize.height;
3507 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3508 if(charSelected && blockAlignEnabled)
3510 rowRight = std::numeric_limits<float>::max();
3512 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3514 selectionState = SelectionNone;
3516 // Still selected? start a new selection
3519 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3520 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3521 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3522 selectionState = SelectionStarted;
3527 // build up highlight(s) with this selection data.
3528 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3529 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3536 // If reached end, and still on selection, then close selection.
3539 if(selectionState == SelectionStarted)
3541 // finished selection.
3543 if(lastIt->mIsNewLineChar)
3545 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3547 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3548 maxRowLeft = std::min(maxRowLeft, min.x);
3549 maxRowRight = std::max(maxRowRight, max.x);
3550 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3551 float rowTop = rowBottom - rowSize.height;
3552 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3556 // Get the top left and bottom right corners.
3557 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3558 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3559 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3561 // Clamp quads so they appear to clip to borders of the whole text.
3562 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3564 // For block-align align Further Clamp quads to max left and right extents
3565 if(blockAlignEnabled)
3567 // BlockAlign: Will adjust highlight to block:
3569 // H[ello] (top row right = max of all rows right)
3570 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3571 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3572 // [text] (bottom row left = min of all rows left)
3573 // (common in SMS messaging selection)
3575 // As opposed to the default which is tight text highlighting.
3580 // (common in regular text editors/web browser selection)
3582 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3585 // Finally clamp quads again so they don't exceed the boundry of the control.
3586 const Vector3& controlSize = GetControlSize();
3587 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3590 return mNewHighlightInfo;
3593 void TextInput::UpdateHighlight()
3595 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3597 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3599 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3600 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3601 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3602 // [BOTTOM] [ MIDDLE ]
3605 // Each quad is created as 2 triangles.
3606 // Middle is just 1 quad regardless of its size.
3620 if ( mHighlightMeshActor )
3622 // vertex and triangle buffers should always be present if MeshActor is alive.
3623 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3624 MeshData::VertexContainer vertices;
3625 Dali::MeshData::FaceIndices faceIndices;
3627 if( !newHighlightInfo.mQuadList.empty() )
3629 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3630 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3632 // vertex position defaults to (0 0 0)
3633 MeshData::Vertex vertex;
3634 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3637 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3639 // Add each quad geometry (a sub-selection) to the mesh data.
3649 QuadCoordinates& quad = *iter;
3651 vertex.x = quad.min.x;
3652 vertex.y = quad.min.y;
3653 vertices.push_back( vertex );
3656 vertex.x = quad.max.x;
3657 vertex.y = quad.min.y;
3658 vertices.push_back( vertex );
3660 // bottom-left (v+2)
3661 vertex.x = quad.min.x;
3662 vertex.y = quad.max.y;
3663 vertices.push_back( vertex );
3665 // bottom-right (v+3)
3666 vertex.x = quad.max.x;
3667 vertex.y = quad.max.y;
3668 vertices.push_back( vertex );
3670 // triangle A (3, 1, 0)
3671 faceIndices.push_back( v + 3 );
3672 faceIndices.push_back( v + 1 );
3673 faceIndices.push_back( v );
3675 // triangle B (0, 2, 3)
3676 faceIndices.push_back( v );
3677 faceIndices.push_back( v + 2 );
3678 faceIndices.push_back( v + 3 );
3680 mMeshData.SetFaceIndices( faceIndices );
3683 BoneContainer bones(0); // passed empty as bones not required
3684 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3685 mHighlightMesh.UpdateMeshData(mMeshData);
3690 void TextInput::ClearPopup()
3692 mPopUpPanel.Clear();
3695 void TextInput::AddPopupOptions()
3697 mPopUpPanel.AddPopupOptions();
3700 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3702 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3705 void TextInput::SetPopupPosition(const Vector3& position)
3707 mPopUpPanel.Self().SetPosition( position );
3710 void TextInput::HidePopup(bool animate, bool signalFinished )
3712 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3714 mPopUpPanel.Hide( animate );
3716 if( animate && signalFinished )
3718 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3723 void TextInput::ShowPopup(bool animate)
3727 if(mHighlightMeshActor && mState == StateEdit)
3731 // When text is selected, show popup above top handle (and text), or below bottom handle.
3732 // topHandle: referring to the top most point of the handle or the top line of selection.
3733 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3735 topHandle = mSelectionHandleOneActualPosition;
3736 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3740 topHandle = mSelectionHandleTwoActualPosition;
3741 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3743 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3744 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3746 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3747 Vector3 bottomHandle;
3748 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3749 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3750 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3754 // When no text is selected, show popup at world position of grab handle or cursor
3755 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3756 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3757 position.y -= rowSize.height;
3758 // if can't be positioned above, then position below row.
3759 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3762 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3763 // If grab handle enabled then position pop-up below the grab handle.
3765 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3768 // reposition popup above the desired cursor posiiton.
3769 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3770 textViewSize.z = 0.0f;
3771 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3772 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3774 SetPopupPosition( worldPosition );
3777 mPopUpPanel.Show(animate);
3778 StartMonitoringStageForTouch();
3780 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3783 void TextInput::ShowPopupCutCopyPaste()
3787 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3788 // Check the selected text is whole text or not.
3789 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3791 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3794 if ( !mStyledText.empty() )
3797 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCopy, true );
3798 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3801 if( mClipboard.NumberOfItems() )
3803 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3804 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3809 mPopUpPanel.Hide(false);
3813 void TextInput::SetUpPopUpSelection()
3816 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3817 // If no text exists then don't offer to select
3818 if ( !mStyledText.empty() )
3820 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3821 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelect, true );
3822 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3824 // if clipboard has valid contents then offer paste option
3825 if( mClipboard.NumberOfItems() )
3827 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3828 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3833 mPopUpPanel.Hide(false);
3836 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3841 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3842 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3843 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3844 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3846 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3848 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3850 float closestYdifference = std::numeric_limits<float>::max();
3851 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3852 std::size_t numberOfMatchedCharacters = 0;
3854 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3855 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3857 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3859 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3860 float baselinePosition = info.mPosition.y - info.mDescender;
3862 if( info.mIsVisible )
3864 // store difference between source y point and the y position of the current character
3865 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3867 if( currentYdifference < closestYdifference )
3869 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3870 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3871 closestYdifference = currentYdifference;
3872 matchedCharacters.clear();
3873 numberOfMatchedCharacters = 0; // reset count
3876 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3877 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3879 // ignore new line character.
3880 if( !info.mIsNewLineChar )
3882 matchedCharacters.push_back( info );
3883 numberOfMatchedCharacters++;
3887 } // End of loop checking each character's y position in the character layout table
3889 // Check if last character is a newline, if it is
3890 // then need pretend there is an imaginary line afterwards,
3891 // and check if user is touching below previous line.
3892 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3894 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3896 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3900 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3901 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3903 bool matched( false );
3905 // 2 Iterate through matching list of y positions and find closest matching X position.
3906 for( ; it != endIt; ++it )
3908 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3910 if( info.mIsVisible )
3912 // stop when on left side of character's center.
3913 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3914 if( sourceScrollOffset.x < characterMidPointPosition )
3916 if(info.mIsRightToLeftCharacter)
3918 rightToLeftChar = true;
3920 glyphIntersection = info.mPosition.x;
3925 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3931 rightToLeftChar = lastRightToLeftChar;
3934 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3935 closestIndex = lineOffset + matchCharacterIndex;
3937 mClosestCursorPositionEOL = false; // reset
3938 if ( it == endIt && !matched )
3940 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3943 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3944 if( rightToLeftChar && lastRightToLeftChar )
3946 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3951 // closestIndex is the visual index, need to convert it to the logical index
3952 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3954 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3956 // Checks for situations where user is touching between LTR and RTL
3957 // characters. To identify if the user means the end of a LTR string
3958 // or the beginning of an RTL string, and vice versa.
3959 if( closestIndex > 0 )
3961 if( rightToLeftChar && !lastRightToLeftChar )
3966 // A: In this touch range, the user is indicating that they wish to place
3967 // the cursor at the end of the LTR text.
3968 // B: In this touch range, the user is indicating that they wish to place
3969 // the cursor at the end of the RTL text.
3971 // Result of touching A area:
3972 // [.....LTR]|[RTL......]+
3974 // |: primary cursor (for typing LTR chars)
3975 // +: secondary cursor (for typing RTL chars)
3977 // Result of touching B area:
3978 // [.....LTR]+[RTL......]|
3980 // |: primary cursor (for typing RTL chars)
3981 // +: secondary cursor (for typing LTR chars)
3983 if( sourceScrollOffset.x < glyphIntersection )
3988 else if( !rightToLeftChar && lastRightToLeftChar )
3990 if( sourceScrollOffset.x < glyphIntersection )
3997 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3998 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3999 // one further ahead
4000 if( rightToLeftChar && !lastRightToLeftChar )
4005 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
4007 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
4009 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
4018 float TextInput::GetLineJustificationPosition() const
4020 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4021 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4022 float alignmentOffset = 0.f;
4024 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4025 if( alignment & Toolkit::Alignment::HorizontalLeft )
4027 alignmentOffset = 0.f;
4029 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4031 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4033 else if( alignment & Toolkit::Alignment::HorizontalRight )
4035 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4038 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4039 float justificationOffset = 0.f;
4041 switch( justification )
4043 case Toolkit::TextView::Left:
4045 justificationOffset = 0.f;
4048 case Toolkit::TextView::Center:
4050 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4053 case Toolkit::TextView::Right:
4055 justificationOffset = mTextLayoutInfo.mTextSize.width;
4058 case Toolkit::TextView::Justified:
4060 justificationOffset = 0.f;
4065 DALI_ASSERT_ALWAYS( false );
4069 return alignmentOffset + justificationOffset;
4072 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4074 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4075 A newline character is not inserted in this case */
4077 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4079 Vector3 cursorPosition;
4081 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4083 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4085 // end character so use
4086 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4087 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4091 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4094 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4096 // If previous character on a different line then use current characters position
4097 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4099 if ( mClosestCursorPositionEOL )
4101 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4105 cursorPosition = Vector3(currentCharInfo.mPosition);
4110 // Previous character is on same line so use position of previous character plus it's width.
4111 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4114 return cursorPosition;
4117 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4119 bool direction(false);
4120 Vector3 alternatePosition;
4121 bool alternatePositionValid(false);
4123 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4126 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4128 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4130 alternatePositionValid = false;
4131 directionRTL = false;
4133 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4135 std::size_t visualCharacterPosition;
4137 // When cursor is not at beginning, consider possibility of
4138 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4139 if(characterPosition > 0)
4141 // Cursor position should be the end of the last character.
4142 // If the last character is LTR, then the end is on the right side of the glyph.
4143 // If the last character is RTL, then the end is on the left side of the glyph.
4144 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4146 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4148 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4151 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4152 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4154 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4155 const Vector3& size = GetControlSize();
4157 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4159 --visualCharacterPosition;
4161 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4164 if(!info.mIsNewLineChar)
4166 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4170 // When cursor points to first character on new line, position cursor at the start of this glyph.
4171 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4173 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4174 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4175 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4177 cursorPosition.x = infoNext.mPosition.x + start;
4178 cursorPosition.y = infoNext.mPosition.y;
4182 // If cursor points to the end of text, then can only position
4183 // cursor where the new line starts based on the line-justification position.
4184 cursorPosition.x = GetLineJustificationPosition();
4186 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4188 // If this is after the last character, then we can assume that the new cursor
4189 // should be exactly one row below the current row.
4191 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4192 cursorPosition.y = info.mPosition.y + rowRect.height;
4196 // If this is not after last character, then we can use this row's height.
4197 // should be exactly one row below the current row.
4199 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4200 cursorPosition.y = info.mPosition.y + rowRect.height;
4205 directionRTL = info.mIsRightToLeftCharacter;
4207 // 1. When the cursor is neither at the beginning or the end,
4208 // we can show multiple cursors under situations when the cursor is
4209 // between RTL and LTR text...
4210 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4212 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4214 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4215 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4217 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4219 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4220 // Text: [...LTR...]|[...RTL...]
4222 // Alternate cursor pos: ^
4223 // In which case we need to display an alternate cursor for the RTL text.
4225 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4226 alternatePosition.y = infoAlt.mPosition.y;
4227 alternatePositionValid = true;
4229 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4231 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4232 // Text: |[...RTL...] [...LTR....]
4234 // Alternate cursor pos: ^
4235 // In which case we need to display an alternate cursor for the RTL text.
4237 alternatePosition.x = infoAlt.mPosition.x;
4238 alternatePosition.y = infoAlt.mPosition.y;
4239 alternatePositionValid = true;
4244 // 2. When the cursor is at the end of the text,
4245 // and we have multi-directional text,
4246 // we can also consider showing mulitple cursors.
4247 // The rule here is:
4248 // If first and last characters on row are different
4249 // Directions, then two cursors need to be displayed.
4251 // Get first logical glyph on row
4252 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4254 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4255 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4257 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4259 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4260 // Text: [...LTR...]|[...RTL...]
4262 // Alternate cursor pos: ^
4263 // In which case we need to display an alternate cursor for the RTL text, this cursor
4264 // should be at the end of the given line.
4266 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4267 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4268 alternatePosition.y = infoAlt.mPosition.y;
4269 alternatePositionValid = true;
4271 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4273 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4274 // Text: |[...RTL...] [...LTR....]
4276 // Alternate cursor pos: ^
4277 // In which case we need to display an alternate cursor for the RTL text.
4279 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4280 alternatePosition.x = infoAlt.mPosition.x;
4281 alternatePosition.y = infoAlt.mPosition.y;
4282 alternatePositionValid = true;
4285 } // characterPosition > 0
4286 else if(characterPosition == 0)
4288 // When the cursor position is at the beginning, it should be at the start of the current character.
4289 // If the current character is LTR, then the start is on the right side of the glyph.
4290 // If the current character is RTL, then the start is on the left side of the glyph.
4291 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4293 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4295 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4298 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4299 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4301 cursorPosition.x = info.mPosition.x + start;
4302 cursorPosition.y = info.mPosition.y;
4303 directionRTL = info.mIsRightToLeftCharacter;
4308 // If the character table is void, place the cursor accordingly the text alignment.
4309 const Vector3& size = GetControlSize();
4311 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4312 float alignmentOffset = 0.f;
4314 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4315 if( alignment & Toolkit::Alignment::HorizontalLeft )
4317 alignmentOffset = 0.f;
4319 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4321 alignmentOffset = 0.5f * ( size.width );
4323 else if( alignment & Toolkit::Alignment::HorizontalRight )
4325 alignmentOffset = size.width;
4328 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4329 cursorPosition.x = alignmentOffset;
4331 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4332 if( alignment & Toolkit::Alignment::VerticalTop )
4334 cursorPosition.y = mLineHeight;
4336 else if( alignment & Toolkit::Alignment::VerticalCenter )
4338 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4340 else if( alignment & Toolkit::Alignment::VerticalBottom )
4342 cursorPosition.y = size.height;
4346 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4347 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4348 if( alternatePositionValid )
4350 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4351 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4354 return cursorPosition;
4357 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4359 // scan string from current position to beginning of current line to note direction of line
4360 while(logicalPosition)
4363 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4364 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4371 return logicalPosition;
4374 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4378 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4381 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4383 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4384 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4386 min = Vector2::ZERO;
4387 max = Vector2(0.0f, mLineHeight);
4391 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4392 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4393 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4395 // If cursor is pointing to end of line, then start from last character.
4396 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4398 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4400 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4401 if( !it->mIsVisible )
4403 characterPosition = FindVisibleCharacter( Left, characterPosition );
4404 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4407 // Scan characters left and right of cursor, stopping when end of line/string reached or
4408 // y position greater than threshold of reference line.
4410 // 1. scan left until we reach the beginning or a different line.
4411 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4412 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4413 // min-x position is the left-most char's left (x)
4414 // max-x position is the right-most char's right (x)
4415 // min-y position is the minimum of all character's top (y)
4416 // max-y position is the maximum of all character's bottom (y+height)
4417 min.y = validCharIt->mPosition.y;
4418 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4423 min.y = std::min(min.y, validCharIt->mPosition.y);
4424 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4433 if( (it->mPosition.y < referenceLine) ||
4434 (it->mIsNewLineChar) ||
4441 // info refers to the first character on this line.
4442 min.x = validCharIt->mPosition.x;
4444 // 2. scan right until we reach end or a different line
4445 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4446 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4450 if( (it->mPosition.y > referenceLine) ||
4451 (it->mIsNewLineChar) ||
4458 min.y = std::min(min.y, validCharIt->mPosition.y);
4459 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4464 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4466 if ( validCharIt != end )
4468 // info refers to the last character on this line.
4469 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4472 return Size( max.x - min.x, max.y - min.y );
4475 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4477 Actor popUpPanel = mPopUpPanel.GetRootActor();
4479 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4485 Dali::Actor parent( touchedActor.GetParent() );
4489 return WasTouchedCheck( parent );
4496 void TextInput::StartMonitoringStageForTouch()
4498 Stage stage = Stage::GetCurrent();
4499 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4502 void TextInput::EndMonitoringStageForTouch()
4504 Stage stage = Stage::GetCurrent();
4505 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4508 void TextInput::OnStageTouched(const TouchEvent& event)
4510 if( event.GetPointCount() > 0 )
4512 if ( TouchPoint::Down == event.GetPoint(0).state )
4514 const Actor touchedActor(event.GetPoint(0).hitActor);
4516 bool popUpShown( false );
4518 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4523 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4525 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4527 EndMonitoringStageForTouch();
4528 HidePopup( true, false );
4531 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4533 EndMonitoringStageForTouch();
4534 ShowGrabHandleAndSetVisibility( false );
4540 void TextInput::SelectText(std::size_t start, std::size_t end)
4542 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4543 IsGrabHandleEnabled()?"true":"false",
4544 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4545 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4546 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4548 StartMonitoringStageForTouch();
4550 if ( mEditModeActive ) // Only allow text selection when in edit mode
4552 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4553 mSelectingText = true;
4555 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4557 ImfManager imfManager = ImfManager::Get();
4560 imfManager.SetCursorPosition ( mCursorPosition );
4561 imfManager.SetSurroundingText( GetText() );
4562 imfManager.NotifyCursorPosition();
4564 // 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.
4566 // Hide grab handle when selecting.
4567 ShowGrabHandleAndSetVisibility( false );
4569 if( start != end ) // something to select
4571 SetCursorVisibility( false );
4572 StopCursorBlinkTimer();
4574 CreateSelectionHandles(start, end);
4577 const TextStyle oldInputStyle( mInputStyle );
4578 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4580 if( oldInputStyle != mInputStyle )
4582 // Updates the line height accordingly with the input style.
4585 EmitStyleChangedSignal();
4591 mSelectingText = false;
4595 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4597 MarkupProcessor::StyledTextArray currentSelectedText;
4599 if ( IsTextSelected() )
4601 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4602 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4604 for(; it != end; ++it)
4606 MarkupProcessor::StyledText& styledText( *it );
4607 currentSelectedText.push_back( styledText );
4610 return currentSelectedText;
4613 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4615 const std::size_t beginIndex = std::min( begin, end );
4616 const std::size_t endIndex = std::max( begin, end );
4619 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4621 // Create a styled text array used to replace the text into the text-view.
4622 MarkupProcessor::StyledTextArray text;
4623 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4625 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4626 GetTextLayoutInfo();
4628 if( IsScrollEnabled() )
4630 // Need to set the scroll position as the text's size may have changed.
4631 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4634 ShowGrabHandleAndSetVisibility( false );
4640 // Set Handle positioning as the new style may have repositioned the characters.
4641 SetSelectionHandlePosition(HandleOne);
4642 SetSelectionHandlePosition(HandleTwo);
4645 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4647 // Just hide the grab handle when keyboard is hidden.
4648 if (!keyboardShown )
4650 ShowGrabHandleAndSetVisibility( false );
4652 // If the keyboard is not now being shown, then hide the popup panel
4653 mPopUpPanel.Hide( true );
4657 // Removes highlight and resumes edit mode state
4658 void TextInput::RemoveHighlight()
4660 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4662 if ( mHighlightMeshActor )
4664 if ( mSelectionHandleOne )
4666 mActiveLayer.Remove( mSelectionHandleOne );
4667 mSelectionHandleOne.Reset();
4668 mSelectionHandleOneOffset.x = 0.0f;
4670 if ( mSelectionHandleTwo )
4672 mActiveLayer.Remove( mSelectionHandleTwo );
4673 mSelectionHandleTwo.Reset();
4674 mSelectionHandleTwoOffset.x = 0.0f;
4677 mNewHighlightInfo.mQuadList.clear();
4679 Self().Remove( mHighlightMeshActor );
4681 SetCursorVisibility( true );
4682 StartCursorBlinkTimer();
4684 mHighlightMeshActor.Reset();
4685 // NOTE: We cannot dereference mHighlightMesh, due
4686 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4691 mSelectionHandleOnePosition = 0;
4692 mSelectionHandleTwoPosition = 0;
4695 void TextInput::CreateHighlight()
4697 if ( !mHighlightMeshActor )
4699 mMeshData = MeshData( );
4700 mMeshData.SetHasNormals( true );
4702 mCustomMaterial = Material::New("CustomMaterial");
4703 mCustomMaterial.SetDiffuseColor( mMaterialColor );
4705 mMeshData.SetMaterial( mCustomMaterial );
4707 mHighlightMesh = Mesh::New( mMeshData );
4709 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4710 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4711 mHighlightMeshActor.SetInheritShaderEffect( false );
4712 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4713 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4714 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4715 mHighlightMeshActor.SetAffectedByLighting(false);
4717 Self().Add(mHighlightMeshActor);
4722 bool TextInput::CopySelectedTextToClipboard()
4724 mCurrentCopySelecton.clear();
4726 mCurrentCopySelecton = GetSelectedText();
4728 std::string stringToStore;
4730 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4731 * a marked up string.
4733 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4734 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4735 bool success = mClipboard.SetItem( stringToStore );
4739 void TextInput::PasteText( const Text& text )
4741 // Update Flag, indicates whether to update the text-input contents or not.
4742 // Any key stroke that results in a visual change of the text-input should
4743 // set this flag to true.
4744 bool update = false;
4745 if( mHighlightMeshActor )
4747 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4748 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4750 ImfManager imfManager = ImfManager::Get();
4753 imfManager.SetCursorPosition( mCursorPosition );
4754 imfManager.NotifyCursorPosition();
4756 DeleteHighlightedText( true );
4760 bool textExceedsMaximunNumberOfCharacters = false;
4761 bool textExceedsBoundary = false;
4763 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4765 mCursorPosition += insertedStringLength;
4766 ImfManager imfManager = ImfManager::Get();
4769 imfManager.SetCursorPosition ( mCursorPosition );
4770 imfManager.NotifyCursorPosition();
4773 update = update || ( insertedStringLength > 0 );
4779 if( insertedStringLength < text.GetLength() )
4781 EmitMaxInputCharactersReachedSignal();
4784 if( textExceedsBoundary )
4786 EmitInputTextExceedsBoundariesSignal();
4790 void TextInput::SetTextDirection()
4792 // Put the cursor to the right if we are empty and an RTL language is being used.
4793 if ( mStyledText.empty() )
4795 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4797 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4798 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4800 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4801 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4803 int alignment( mDisplayedTextView.GetTextAlignment() &
4804 ( Toolkit::Alignment::VerticalTop |
4805 Toolkit::Alignment::VerticalCenter |
4806 Toolkit::Alignment::VerticalBottom |
4807 Toolkit::Alignment::HorizontalCenter ) );
4808 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4810 // If our alignment is in the center, then do not change.
4811 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4813 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4816 // If our justification is in the center, then do not change.
4817 if ( justification != Toolkit::TextView::Center )
4819 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4822 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4823 mDisplayedTextView.SetLineJustification( justification );
4827 void TextInput::UpdateLineHeight()
4829 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4830 mLineHeight = font.GetLineHeight();
4832 // 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.
4834 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4836 if( !mExceedEnabled || shrink )
4838 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4842 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4844 std::size_t position = 0;
4846 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4852 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4854 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4856 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4862 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4863 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4865 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4871 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4876 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4883 void TextInput::SetSortModifier( float depthOffset )
4885 if(mDisplayedTextView)
4887 mDisplayedTextView.SetSortModifier(depthOffset);
4891 void TextInput::SetSnapshotModeEnabled( bool enable )
4893 if(mDisplayedTextView)
4895 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4899 bool TextInput::IsSnapshotModeEnabled() const
4901 bool snapshotEnabled = false;
4903 if(mDisplayedTextView)
4905 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4908 return snapshotEnabled;
4911 void TextInput::SetMarkupProcessingEnabled( bool enable )
4913 mMarkUpEnabled = enable;
4916 bool TextInput::IsMarkupProcessingEnabled() const
4918 return mMarkUpEnabled;
4921 void TextInput::SetScrollEnabled( bool enable )
4923 if( mDisplayedTextView )
4925 mDisplayedTextView.SetScrollEnabled( enable );
4930 // Don't set cursor's and handle's visibility to false if they are outside the
4931 // boundaries of the text-input.
4932 mIsCursorInScrollArea = true;
4933 mIsGrabHandleInScrollArea = true;
4934 if( mSelectionHandleOne && mSelectionHandleTwo )
4936 mSelectionHandleOne.SetVisible( true );
4937 mSelectionHandleTwo.SetVisible( true );
4939 if( mHighlightMeshActor )
4941 mHighlightMeshActor.SetVisible( true );
4947 bool TextInput::IsScrollEnabled() const
4949 bool scrollEnabled = false;
4951 if( mDisplayedTextView )
4953 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4956 return scrollEnabled;
4959 void TextInput::SetScrollPosition( const Vector2& position )
4961 if( mDisplayedTextView )
4963 mDisplayedTextView.SetScrollPosition( position );
4967 Vector2 TextInput::GetScrollPosition() const
4969 Vector2 scrollPosition;
4971 if( mDisplayedTextView )
4973 scrollPosition = mDisplayedTextView.GetScrollPosition();
4976 return scrollPosition;
4979 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4981 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4982 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4983 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4985 // Add style to the new input text.
4986 MarkupProcessor::StyledTextArray textToInsert;
4987 for( std::size_t i = 0; i < insertedStringLength; ++i )
4989 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4990 textToInsert.push_back( newStyledCharacter );
4993 //Insert text to the TextView.
4994 const bool emptyTextView = mStyledText.empty();
4995 if( emptyTextView && mPlaceHolderSet )
4997 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4998 mDisplayedTextView.SetText( textToInsert );
5002 if( 0 == numberOfCharactersToReplace )
5004 mDisplayedTextView.InsertTextAt( position, textToInsert );
5008 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
5011 mPlaceHolderSet = false;
5013 if( textToInsert.empty() )
5015 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
5016 GetTextLayoutInfo();
5020 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
5021 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5024 textExceedsBoundary = false;
5026 if( !mExceedEnabled )
5028 const Vector3& size = GetControlSize();
5030 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5032 // If new text does not fit within TextView
5033 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5034 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5035 GetTextLayoutInfo();
5036 textExceedsBoundary = true;
5037 insertedStringLength = 0;
5040 if( textExceedsBoundary )
5042 // Add the part of the text which fits on the text-input.
5044 // Split the text which doesn't fit in two halves.
5045 MarkupProcessor::StyledTextArray firstHalf;
5046 MarkupProcessor::StyledTextArray secondHalf;
5047 SplitText( textToInsert, firstHalf, secondHalf );
5049 // Clear text. This text will be filled with the text inserted.
5050 textToInsert.clear();
5052 // Where to insert the text.
5053 std::size_t positionToInsert = position;
5055 bool end = text.GetLength() <= 1;
5058 // Insert text and check ...
5059 const std::size_t textLength = firstHalf.size();
5060 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5061 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5063 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5065 // Inserted text doesn't fit.
5067 // Remove inserted text
5068 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5069 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5071 // The iteration finishes when only one character doesn't fit.
5072 end = textLength <= 1;
5076 // Prepare next two halves for next iteration.
5077 MarkupProcessor::StyledTextArray copyText = firstHalf;
5078 SplitText( copyText, firstHalf, secondHalf );
5085 // store text to be inserted in mStyledText.
5086 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5088 // Increase the inserted characters counter.
5089 insertedStringLength += textLength;
5091 // Prepare next two halves for next iteration.
5092 MarkupProcessor::StyledTextArray copyText = secondHalf;
5093 SplitText( copyText, firstHalf, secondHalf );
5095 // Update where next text has to be inserted
5096 positionToInsert += textLength;
5102 if( textToInsert.empty() && emptyTextView )
5104 // No character has been added and the text-view was empty.
5105 // Set the placeholder text.
5106 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5107 mPlaceHolderSet = true;
5111 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5112 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5113 mPlaceHolderSet = false;
5116 return insertedStringLength;
5119 void TextInput::GetTextLayoutInfo()
5121 if( mStyledText.empty() )
5123 // The text-input has no text, clear the text-view's layout info.
5124 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5128 if( mDisplayedTextView )
5130 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5134 // There is no text-view.
5135 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5140 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5142 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5146 TextInput& textInputImpl( GetImpl( textInput ) );
5148 switch ( propertyIndex )
5150 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5152 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5155 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5157 textInputImpl.mPopUpPanel.SetCutPastePopUpColor( value.Get< Vector4 >() );
5160 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5162 textInputImpl.mPopUpPanel.SetCutPastePopUpPressedColor( value.Get< Vector4 >() );
5165 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5167 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5170 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5172 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5175 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5177 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5180 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5182 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5185 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5187 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5190 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5192 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5199 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5201 Property::Value value;
5203 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5207 TextInput& textInputImpl( GetImpl( textInput ) );
5209 switch ( propertyIndex )
5211 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5213 value = textInputImpl.GetMaterialDiffuseColor();
5216 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5218 value = textInputImpl.mPopUpPanel.GetCutPastePopUpColor();
5221 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5223 value = textInputImpl.mPopUpPanel.GetCutPastePopUpPressedColor();
5226 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5228 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5231 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5233 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5236 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5238 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5241 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5243 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5246 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5248 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5251 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5253 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5261 void TextInput::EmitStyleChangedSignal()
5263 // emit signal if input style changes.
5264 Toolkit::TextInput handle( GetOwner() );
5265 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5268 void TextInput::EmitTextModified()
5270 // emit signal when text changes.
5271 Toolkit::TextInput handle( GetOwner() );
5272 mTextModifiedSignal.Emit( handle );
5276 void TextInput::EmitMaxInputCharactersReachedSignal()
5278 // emit signal if max characters is reached during text input.
5279 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5281 Toolkit::TextInput handle( GetOwner() );
5282 mMaxInputCharactersReachedSignalV2.Emit( handle );
5285 void TextInput::EmitInputTextExceedsBoundariesSignal()
5287 // Emit a signal when the input text exceeds the boundaries of the text input.
5289 Toolkit::TextInput handle( GetOwner() );
5290 mInputTextExceedBoundariesSignalV2.Emit( handle );
5293 } // namespace Internal
5295 } // namespace Toolkit