2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/dali.h>
20 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
21 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
23 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
25 #include <dali/integration-api/debug.h>
38 #if defined(DEBUG_ENABLED)
39 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
42 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
43 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
44 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
45 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
46 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
47 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
49 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
50 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
51 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
52 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
53 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
54 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
56 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
58 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
59 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
60 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.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;
786 // Set Boundary for Popup so it keeps the Pop-up within the area also.
787 mPopUpPanel.SetPopupBoundary( boundingRectangle );
790 const Rect<float> TextInput::GetBoundingRectangle() const
792 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
794 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
795 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
797 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
802 const Vector4& TextInput::GetSelectionHandleFlipMargin()
804 return mSelectionHandleFlipMargin;
807 void TextInput::SetTextColor( const Vector4& color )
809 mDisplayedTextView.SetColor( color );
812 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
814 if( style != mInputStyle )
817 bool emitSignal = false;
819 // mask: modify style according to mask, if different emit signal.
820 const TextStyle oldInputStyle( mInputStyle );
822 // Copy the new style.
823 mInputStyle.Copy( style, mask );
825 // if style has changed, emit signal.
826 if( oldInputStyle != mInputStyle )
831 // Updates the line height accordingly with the input style.
834 // Changing font point size will require the cursor to be re-sized
839 EmitStyleChangedSignal();
844 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
846 if ( IsTextSelected() )
848 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
849 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
851 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
853 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
856 // Keeps the old style to be compared with the new one.
857 const TextStyle oldInputStyle( mInputStyle );
859 // Copy only those parameters from the style which are set in the mask.
860 mInputStyle.Copy( style, mask );
862 if( mInputStyle != oldInputStyle )
864 // Updates the line height accordingly with the input style.
867 EmitStyleChangedSignal();
872 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
874 if( !mStyledText.empty() )
876 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
880 TextStyle TextInput::GetStyleAtCursor() const
884 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
886 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
887 style = mStyledText.at( mCursorPosition-1 ).mStyle;
893 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
895 Dali::Font defaultFont = Dali::Font::New();
896 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
903 TextStyle TextInput::GetStyleAt( std::size_t position ) const
905 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
907 if( position >= mStyledText.size() )
909 position = mStyledText.size() - 1;
912 return mStyledText.at( position ).mStyle;
915 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
917 mDisplayedTextView.SetTextAlignment( align );
918 mOverrideAutomaticAlignment = true;
921 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
923 mDisplayedTextView.SetLineJustification( justification );
924 mOverrideAutomaticAlignment = true;
927 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
929 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
932 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
934 return mDisplayedTextView.GetFadeBoundary();
937 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
939 return mDisplayedTextView.GetTextAlignment();
942 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
944 mDisplayedTextView.SetMultilinePolicy( policy );
947 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
949 return mDisplayedTextView.GetMultilinePolicy();
952 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
954 mDisplayedTextView.SetWidthExceedPolicy( policy );
957 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
959 return mDisplayedTextView.GetWidthExceedPolicy();
962 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
964 mDisplayedTextView.SetHeightExceedPolicy( policy );
967 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
969 return mDisplayedTextView.GetHeightExceedPolicy();
972 void TextInput::SetExceedEnabled( bool enable )
974 mExceedEnabled = enable;
977 bool TextInput::GetExceedEnabled() const
979 return mExceedEnabled;
982 void TextInput::SetBackground(Dali::Image image )
984 // TODO Should add this function and add public api to match.
987 bool TextInput::OnTouchEvent(const TouchEvent& event)
992 bool TextInput::OnKeyEvent(const KeyEvent& event)
994 switch( event.state )
998 return OnKeyDownEvent(event);
1004 return OnKeyUpEvent(event);
1016 void TextInput::OnKeyInputFocusGained()
1018 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1020 mEditModeActive = true;
1022 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1024 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1026 // Updates the line height accordingly with the input style.
1029 // Connect the signals to use in text input.
1030 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1031 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1033 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1036 GetTextLayoutInfo();
1039 SetCursorVisibility( true );
1040 StartCursorBlinkTimer();
1042 Toolkit::TextInput handle( GetOwner() );
1043 mInputStartedSignalV2.Emit( handle );
1045 ImfManager imfManager = ImfManager::Get();
1049 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1051 // Notify that the text editing start.
1052 imfManager.Activate();
1054 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1055 imfManager.SetRestoreAferFocusLost( true );
1057 imfManager.SetCursorPosition( mCursorPosition );
1058 imfManager.NotifyCursorPosition();
1061 mClipboard = Clipboard::Get(); // Store handle to clipboard
1063 // Now in edit mode we can accept string to paste from clipboard
1064 if( Adaptor::IsAvailable() )
1066 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1069 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1074 void TextInput::OnKeyInputFocusLost()
1076 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1080 // If key input focus is lost, it removes the
1081 // underline from the last pre-edit text.
1082 RemovePreEditStyle();
1083 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1084 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1088 ImfManager imfManager = ImfManager::Get();
1091 // The text editing is finished. Therefore the imf manager don't have restore activation.
1092 imfManager.SetRestoreAferFocusLost( false );
1094 // Notify that the text editing finish.
1095 imfManager.Deactivate();
1097 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1099 // Disconnect signal used the text input.
1100 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1102 Toolkit::TextInput handle( GetOwner() );
1103 mInputFinishedSignalV2.Emit( handle );
1104 mEditModeActive = false;
1105 mPreEditFlag = false;
1107 SetCursorVisibility( false );
1108 StopCursorBlinkTimer();
1110 ShowGrabHandleAndSetVisibility( false );
1113 // No longer in edit mode so do not want to receive string from clipboard
1114 if( Adaptor::IsAvailable() )
1116 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1119 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1121 Clipboard clipboard = Clipboard::Get();
1125 clipboard.HideClipboard();
1130 void TextInput::OnControlStageConnection()
1132 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1134 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1136 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1140 void TextInput::CreateActiveLayer()
1142 Actor self = Self();
1143 mActiveLayer = Layer::New();
1145 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1146 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1147 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1149 self.Add( mActiveLayer );
1150 mActiveLayer.RaiseToTop();
1153 void TextInput::OnInitialize()
1155 CreateTextViewActor();
1159 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1160 // different positions depending on language)
1161 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1162 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1163 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1165 Actor self = Self();
1166 self.Add( mCursor );
1167 self.Add( mCursorRTL );
1169 mCursorVisibility = false;
1171 CreateActiveLayer(); // todo move this so layer only created when needed.
1173 // Assign names to image actors
1174 mCursor.SetName("mainCursor");
1175 mCursorRTL.SetName("rtlCursor");
1178 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1180 mDisplayedTextView.SetSize( targetSize );
1181 GetTextLayoutInfo();
1182 mActiveLayer.SetSize(targetSize);
1185 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1187 Relayout( mDisplayedTextView, size, container );
1188 GetTextLayoutInfo();
1193 Vector3 TextInput::GetNaturalSize()
1195 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1197 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1199 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1200 naturalSize.height = mLineHeight;
1206 float TextInput::GetHeightForWidth( float width )
1208 float height = mDisplayedTextView.GetHeightForWidth( width );
1210 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1212 // If the height is zero, it means there is no text. Let's return the cursor height.
1213 height = mLineHeight;
1219 /*end of Virtual methods from parent*/
1221 // Private Internal methods
1223 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1225 switch (gesture.state)
1227 case Gesture::Started:
1228 // fall through so code not duplicated
1229 case Gesture::Continuing:
1231 if (actor == mGrabArea)
1233 SetCursorVisibility( true );
1234 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1235 MoveGrabHandle( gesture.displacement );
1236 HidePopup(); // Do not show popup whilst handle is moving
1238 else if (actor == mHandleOneGrabArea)
1240 // the displacement in PanGesture is affected by the actor's rotation.
1241 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1242 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1244 MoveSelectionHandle( HandleOne, gesture.displacement );
1246 mState = StateDraggingHandle;
1249 else if (actor == mHandleTwoGrabArea)
1251 // the displacement in PanGesture is affected by the actor's rotation.
1252 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1253 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1255 MoveSelectionHandle( HandleTwo, gesture.displacement );
1257 mState = StateDraggingHandle;
1263 case Gesture::Finished:
1265 // Revert back to non-pressed selection handle images
1266 if (actor == mGrabArea)
1268 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1269 SetCursorVisibility( true );
1270 SetUpPopUpSelection();
1273 if (actor == mHandleOneGrabArea)
1275 // the displacement in PanGesture is affected by the actor's rotation.
1276 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1277 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1279 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1281 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1283 ShowPopupCutCopyPaste();
1285 if (actor == mHandleTwoGrabArea)
1287 // the displacement in PanGesture is affected by the actor's rotation.
1288 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1289 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1291 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1293 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1295 ShowPopupCutCopyPaste();
1304 // Stop the flashing animation so easy to see when moved.
1305 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1307 if (touch.GetPoint(0).state == TouchPoint::Down)
1309 SetCursorVisibility( true );
1310 StopCursorBlinkTimer();
1312 else if (touch.GetPoint(0).state == TouchPoint::Up)
1314 SetCursorVisibility( true );
1315 StartCursorBlinkTimer();
1320 // selection handle one
1321 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1323 if (touch.GetPoint(0).state == TouchPoint::Down)
1325 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1327 else if (touch.GetPoint(0).state == TouchPoint::Up)
1329 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1334 // selection handle two
1335 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1337 if (touch.GetPoint(0).state == TouchPoint::Down)
1339 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1341 else if (touch.GetPoint(0).state == TouchPoint::Up)
1343 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1348 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1350 // If text exists then select nearest word.
1351 if ( !mStyledText.empty())
1355 ShowGrabHandleAndSetVisibility( false );
1360 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1361 // converts the pre-edit word being displayed to a committed word.
1362 if ( !mUnderlinedPriorToPreEdit )
1365 style.SetUnderline( false );
1366 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1368 mPreEditFlag = false;
1369 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1370 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1371 PreEditReset( false );
1373 mCursorPosition = 0;
1375 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1376 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1378 ImfManager imfManager = ImfManager::Get();
1381 imfManager.SetCursorPosition ( mCursorPosition );
1382 imfManager.NotifyCursorPosition();
1385 std::size_t start = 0;
1386 std::size_t end = 0;
1387 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1389 SelectText( start, end );
1391 // if no text but clipboard has content then show paste option
1392 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1394 ShowPopupCutCopyPaste();
1397 // If no text and clipboard empty then do nothing
1400 // TODO: Change the function name to be more general.
1401 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1403 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1404 , (mEditOnTouch)?"true":"false"
1405 , (mEditModeActive)?"true":"false");
1407 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1412 if( mGrabArea == actor )
1414 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1416 SetUpPopUpSelection();
1426 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1428 // Initially don't create the grab handle.
1429 bool createGrabHandle = false;
1431 if ( !mEditModeActive )
1433 // update line height before calculate the actual position.
1436 // Only start edit mode if TextInput configured to edit on touch
1439 // Set the initial cursor position in the tap point.
1440 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1442 // Create the grab handle.
1443 // TODO Make this a re-usable function.
1444 if ( IsGrabHandleEnabled() )
1446 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1450 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1451 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1452 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1453 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1457 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1458 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1459 // otherwise the Grab handle will be shown when selecting.
1466 // Show the keyboard if it was hidden.
1467 if (!VirtualKeyboard::IsVisible())
1469 VirtualKeyboard::Show();
1472 // Reset keyboard as tap event has occurred.
1473 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1474 PreEditReset( true );
1476 GetTextLayoutInfo();
1478 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1480 // 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.
1482 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1484 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1486 // Notify keyboard so it can 're-capture' word for predictive text.
1487 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1488 ImfManager imfManager = ImfManager::Get();
1491 imfManager.SetCursorPosition ( mCursorPosition );
1492 imfManager.NotifyCursorPosition();
1494 const TextStyle oldInputStyle( mInputStyle );
1496 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1500 // Create the grab handle.
1501 // Grab handle is created later.
1502 createGrabHandle = true;
1504 if( oldInputStyle != mInputStyle )
1506 // Updates the line height accordingly with the input style.
1509 EmitStyleChangedSignal();
1514 if ( createGrabHandle && IsGrabHandleEnabled() )
1516 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1520 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1521 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1522 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1523 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1528 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1530 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1532 if(longPress.state == Dali::Gesture::Started)
1534 // Start edit mode on long press
1535 if ( !mEditModeActive )
1540 // If text exists then select nearest word.
1541 if ( !mStyledText.empty())
1545 ShowGrabHandleAndSetVisibility( false );
1550 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1551 // converts the pre-edit word being displayed to a committed word.
1552 if ( !mUnderlinedPriorToPreEdit )
1555 style.SetUnderline( false );
1556 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1558 mPreEditFlag = false;
1559 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1560 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1561 PreEditReset( false );
1563 mCursorPosition = 0;
1565 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1566 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1568 ImfManager imfManager = ImfManager::Get();
1571 imfManager.SetCursorPosition ( mCursorPosition );
1572 imfManager.NotifyCursorPosition();
1574 std::size_t start = 0;
1575 std::size_t end = 0;
1576 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1578 SelectText( start, end );
1581 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1582 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1584 ShowPopupCutCopyPaste();
1589 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1591 const Text clipboardText( notifier.GetContent() );
1592 PasteText( clipboardText );
1594 SetCursorVisibility( true );
1595 StartCursorBlinkTimer();
1597 ShowGrabHandleAndSetVisibility( false );
1603 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1605 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1607 const std::string& name = button.GetName();
1609 if(name == TextInputPopup::OPTION_SELECT_WORD)
1611 std::size_t start = 0;
1612 std::size_t end = 0;
1613 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1615 SelectText( start, end );
1617 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1619 SetCursorVisibility(false);
1620 StopCursorBlinkTimer();
1622 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1623 std::size_t start = 0;
1625 SelectText( start, end );
1627 else if(name == TextInputPopup::OPTION_CUT)
1629 bool ret = CopySelectedTextToClipboard();
1633 DeleteHighlightedText( true );
1637 SetCursorVisibility( true );
1638 StartCursorBlinkTimer();
1642 else if(name == TextInputPopup::OPTION_COPY)
1644 CopySelectedTextToClipboard();
1648 SetCursorVisibility( true );
1649 StartCursorBlinkTimer();
1653 else if(name == TextInputPopup::OPTION_PASTE)
1655 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1657 PasteText(retrievedString);
1659 SetCursorVisibility( true );
1660 StartCursorBlinkTimer();
1662 ShowGrabHandleAndSetVisibility( false );
1666 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1668 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1669 // Hence pass the false parameter for signalFinished.
1670 HidePopup( true, false );
1671 mClipboard.ShowClipboard();
1677 bool TextInput::OnCursorBlinkTimerTick()
1680 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1681 if ( mCursorRTLEnabled )
1683 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1685 mCursorBlinkStatus = !mCursorBlinkStatus;
1690 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1692 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1694 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1695 if(mHighlightMeshActor && mState == StateEdit)
1697 ShowPopupCutCopyPaste();
1701 //FIXME this routine needs to be re-written as it contains too many branches.
1702 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1704 std::string keyName = event.keyPressedName;
1705 std::string keyString = event.keyPressed;
1707 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1709 // Do not consume "Tab" and "Escape" keys.
1710 if(keyName == "Tab" || keyName == "Escape")
1712 // Escape key to end the edit mode
1718 HidePopup(); // If Pop-up shown then hides it as editing text.
1720 // Update Flag, indicates whether to update the text-input contents or not.
1721 // Any key stroke that results in a visual change of the text-input should
1722 // set this flag to true.
1725 // Whether to scroll text to cursor position.
1726 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1727 bool scroll = false;
1729 if (keyName == "Return")
1731 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1733 bool preEditFlagPreviouslySet( mPreEditFlag );
1735 if (mHighlightMeshActor)
1737 // replaces highlighted text with new line
1738 DeleteHighlightedText( false );
1740 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1742 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1743 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1746 mCommitByKeyInput = true;
1749 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1750 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1752 mPreEditFlag = true;
1753 mIgnoreCommitFlag = false;
1763 else if ( keyName == "space" )
1765 if ( mHighlightMeshActor )
1767 // Some text is selected so erase it before adding space.
1768 DeleteHighlightedText( true );
1772 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1774 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1775 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1778 mCommitByKeyInput = true;
1783 else if (keyName == "BackSpace")
1785 if ( mHighlightMeshActor )
1787 // Some text is selected so erase it
1788 DeleteHighlightedText( true );
1793 if ( mCursorPosition > 0 )
1795 DeleteCharacter( mCursorPosition );
1801 else if (keyName == "Right")
1806 else if (keyName == "Left")
1808 AdvanceCursor(true);
1811 else // event is a character
1813 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1814 if ( !keyString.empty() )
1816 if ( mHighlightMeshActor )
1818 // replaces highlighted text with new character
1819 DeleteHighlightedText( false );
1823 // Received key String
1824 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1830 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1831 // as this is a costly operation.
1837 if(update || scroll)
1839 if( IsScrollEnabled() )
1841 // Calculates the new cursor position (in actor coordinates)
1842 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1844 ScrollTextViewToMakeCursorVisible( cursorPosition );
1851 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1853 std::string keyName = event.keyPressedName;
1854 std::string keyString = event.keyPressed;
1856 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1858 // The selected text become deselected when the key code is DALI_KEY_BACK.
1859 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1868 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1870 // Updates the stored scroll position.
1871 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1873 const Vector3& controlSize = GetControlSize();
1874 Size cursorSize( CURSOR_THICKNESS, 0.f );
1876 // Updates the cursor and grab handle position and visibility.
1877 if( mGrabHandle || mCursor )
1879 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1880 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1882 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1884 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1888 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1889 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1894 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1895 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1899 // Updates the selection handles and highlighted text position and visibility.
1900 if( mSelectionHandleOne && mSelectionHandleTwo )
1902 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1903 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1904 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1905 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1906 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1907 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1909 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1910 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1912 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1913 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1914 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1915 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1917 if( mHighlightMeshActor )
1919 mHighlightMeshActor.SetVisible( true );
1925 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1927 // Scroll the text to make the cursor visible.
1928 const Size cursorSize( CURSOR_THICKNESS,
1929 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1931 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1933 const Vector3& controlSize = GetControlSize();
1935 // Calculates the new scroll position.
1936 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1937 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1939 scrollOffset.x += cursorPosition.x;
1942 if( cursorPosition.y - cursorSize.height < 0.f )
1944 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1946 else if( cursorPosition.y > controlSize.height )
1948 scrollOffset.y += cursorPosition.y;
1951 // Sets the new scroll position.
1952 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1953 SetScrollPosition( scrollOffset );
1956 void TextInput::StartScrollTimer()
1960 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1961 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1964 if( !mScrollTimer.IsRunning() )
1966 mScrollTimer.Start();
1970 void TextInput::StopScrollTimer()
1974 mScrollTimer.Stop();
1978 bool TextInput::OnScrollTimerTick()
1980 // TODO: need to set the new style accordingly the new handle position.
1982 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1984 // nothing to do if all handles are invisible or doesn't exist.
1990 // Choose between the grab handle or the selection handles.
1991 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1992 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1993 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1995 std::size_t newCursorPosition = 0;
1996 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1998 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1999 // the new selection handle's position needs to be different of the other one.
2000 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2001 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2002 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2004 if( differentSelectionHandles )
2006 handlePosition = newCursorPosition;
2008 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2010 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2012 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2013 scrollPosition += scrollDelta;
2014 SetScrollPosition( scrollPosition );
2016 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2021 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2024 actualHandlePosition.x += mScrollDisplacement.x;
2025 actualHandlePosition.y += mScrollDisplacement.y;
2030 // Public Internal Methods (public for testing purpose)
2032 void TextInput::SetUpTouchEvents()
2034 if ( !mTapDetector )
2036 mTapDetector = TapGestureDetector::New();
2037 // Attach the actors and connect the signal
2038 mTapDetector.Attach(Self());
2040 // As contains children which may register for tap the default control detector is not used.
2041 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2044 if ( !mDoubleTapDetector )
2046 mDoubleTapDetector = TapGestureDetector::New();
2047 mDoubleTapDetector.SetTapsRequired( 2 );
2048 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2050 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2051 // so that we do not, unnecessarily, have a double tap request all the time
2054 if ( !mPanGestureDetector )
2056 mPanGestureDetector = PanGestureDetector::New();
2057 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2060 if ( !mLongPressDetector )
2062 mLongPressDetector = LongPressGestureDetector::New();
2063 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2064 mLongPressDetector.Attach(Self());
2068 void TextInput::CreateTextViewActor()
2070 mDisplayedTextView = Toolkit::TextView::New();
2071 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2072 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2073 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2074 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2075 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2076 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2077 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2078 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2079 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2080 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2082 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2084 Self().Add( mDisplayedTextView );
2087 // Start a timer to initiate, used by the cursor to blink.
2088 void TextInput::StartCursorBlinkTimer()
2090 if ( !mCursorBlinkTimer )
2092 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2093 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2096 if ( !mCursorBlinkTimer.IsRunning() )
2098 mCursorBlinkTimer.Start();
2102 // Start a timer to initiate, used by the cursor to blink.
2103 void TextInput::StopCursorBlinkTimer()
2105 if ( mCursorBlinkTimer )
2107 mCursorBlinkTimer.Stop();
2111 void TextInput::StartEditMode()
2113 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2115 if(!mEditModeActive)
2120 if ( mDoubleTapDetector )
2122 mDoubleTapDetector.Attach( Self() );
2126 void TextInput::EndEditMode()
2128 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2130 ClearKeyInputFocus();
2132 if ( mDoubleTapDetector )
2134 mDoubleTapDetector.Detach( Self() );
2138 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2140 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2142 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2144 style.SetUnderline( true );
2145 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2149 void TextInput::RemovePreEditStyle()
2151 if ( !mUnderlinedPriorToPreEdit )
2154 style.SetUnderline( false );
2155 SetActiveStyle( style, TextStyle::UNDERLINE );
2159 // IMF related methods
2162 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2164 bool update( false );
2165 bool preeditResetRequired ( false );
2167 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2169 HidePopup(); // If Pop-up shown then hides it as editing text.
2172 switch ( imfEvent.eventName )
2174 case ImfManager::PREEDIT:
2176 mIgnoreFirstCommitFlag = false;
2178 // 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
2179 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2181 // replaces highlighted text with new character
2182 DeleteHighlightedText( false );
2185 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2187 if( IsScrollEnabled() )
2189 // Calculates the new cursor position (in actor coordinates)
2190 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2191 ScrollTextViewToMakeCursorVisible( cursorPosition );
2198 case ImfManager::COMMIT:
2200 if( mIgnoreFirstCommitFlag )
2202 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2203 mIgnoreFirstCommitFlag = false;
2207 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2209 // 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
2210 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2212 // replaces highlighted text with new character
2213 DeleteHighlightedText( false );
2216 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2217 // not needed, one such scenario is when the pre-edit word is too long to fit.
2218 if ( !mIgnoreCommitFlag )
2220 update = CommitReceived( imfEvent.predictiveString );
2224 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2230 if( IsScrollEnabled() )
2232 // Calculates the new cursor position (in actor coordinates)
2233 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2235 ScrollTextViewToMakeCursorVisible( cursorPosition );
2240 case ImfManager::DELETESURROUNDING:
2242 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2243 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2245 mPreEditFlag = false;
2247 std::size_t toDelete = 0;
2248 std::size_t numberOfCharacters = 0;
2250 if( mHighlightMeshActor )
2252 // delete highlighted text.
2253 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2254 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2258 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2260 toDelete = mCursorPosition + imfEvent.cursorOffset;
2262 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2264 numberOfCharacters = mStyledText.size() - toDelete;
2268 numberOfCharacters = imfEvent.numberOfChars;
2271 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2272 DeleteRange( toDelete, numberOfCharacters );
2274 mCursorPosition = toDelete;
2275 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2279 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2282 case ImfManager::GETSURROUNDING:
2284 // 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
2285 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2286 if (! ( mHighlightMeshActor || mSelectingText ) )
2288 std::string text( GetText() );
2289 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2291 imfManager.SetCursorPosition( mCursorPosition );
2292 imfManager.SetSurroundingText( text );
2295 if( 0 != mNumberOfSurroundingCharactersDeleted )
2297 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2298 mNumberOfSurroundingCharactersDeleted = 0;
2300 if( mStyledText.empty() )
2302 // Styled text is empty, so set the placeholder text.
2303 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2304 mPlaceHolderSet = true;
2309 case ImfManager::VOID:
2311 DALI_ASSERT_DEBUG( false );
2315 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2317 return callbackData;
2320 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2322 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2324 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2325 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2327 bool preeditResetRequest ( false );
2329 if( mPreEditFlag ) // Already in pre-edit state.
2331 if( mStyledText.size() >= mMaxStringLength )
2333 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2334 // Cannot fit these characters into field, clear pre-edit.
2335 if ( !mUnderlinedPriorToPreEdit )
2338 style.SetUnderline( false );
2339 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2341 mIgnoreCommitFlag = true;
2342 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2343 mPreEditFlag = false;
2344 EmitMaxInputCharactersReachedSignal();
2348 // delete existing pre-edit string
2349 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2351 // Store new pre-edit string
2352 mPreEditString.SetText( keyString );
2354 if ( keyString.empty() )
2356 mPreEditFlag = false;
2357 mCursorPosition = mPreEditStartPosition;
2359 if( mStyledText.empty() )
2361 // Styled text is empty, so set the placeholder text.
2362 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2363 mPlaceHolderSet = true;
2367 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2369 GetTextLayoutInfo();
2374 // Insert new pre-edit string. InsertAt updates the size and position table.
2375 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2376 // 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.
2377 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2378 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2379 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2382 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2386 else // mPreEditFlag not set
2388 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2390 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2391 // new pre-edit so move into pre-edit state by setting flag
2392 mPreEditFlag = true;
2393 mPreEditString.SetText( keyString ); // store new pre-edit string
2394 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2395 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2396 // 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.
2397 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2398 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2399 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2400 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2406 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2410 return preeditResetRequest;
2413 bool TextInput::CommitReceived(const std::string& keyString )
2415 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2416 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2418 bool update( false );
2420 RemovePreEditStyle();
2422 const std::size_t styledTextSize( mStyledText.size() );
2423 if( styledTextSize >= mMaxStringLength )
2425 // Cannot fit these characters into field, clear pre-edit.
2428 mIgnoreCommitFlag = true;
2429 mPreEditFlag = false;
2431 EmitMaxInputCharactersReachedSignal();
2437 // delete existing pre-edit string
2438 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2439 mPreEditFlag = false;
2441 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2442 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2444 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2446 // No need to update cursor position as Cursor location given by touch.
2447 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2448 mPreserveCursorPosition = false;
2452 // Cursor not set by touch so needs to be re-positioned to input more text
2453 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2455 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2456 if ( mCommitByKeyInput )
2458 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2459 mCommitByKeyInput = false;
2465 if ( mSelectTextOnCommit )
2467 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2472 else // mPreEditFlag not set
2474 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2476 if( mStyledText.empty() && mPlaceHolderSet )
2478 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2479 mDisplayedTextView.SetText( "" );
2480 mNumberOfSurroundingCharactersDeleted = 0;
2481 mPlaceHolderSet = false;
2483 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2485 mNumberOfSurroundingCharactersDeleted = 0;
2490 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2495 mSelectTextOnCommit = false;
2497 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2498 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2503 // End of IMF related methods
2505 std::size_t TextInput::DeletePreEdit()
2507 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2509 DALI_ASSERT_DEBUG( mPreEditFlag );
2511 const std::size_t preEditStringLength = mPreEditString.GetLength();
2512 const std::size_t styledTextSize = mStyledText.size();
2514 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2516 // Prevents erase items outside mStyledText bounds.
2517 if( mPreEditStartPosition > styledTextSize )
2519 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2520 mPreEditStartPosition = styledTextSize;
2523 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2525 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2526 endPosition = styledTextSize;
2529 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2531 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2532 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2534 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2536 return preEditStringLength;
2539 void TextInput::PreEditReset( bool preserveCursorPosition )
2541 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2542 preserveCursorPosition, mCursorPosition);
2544 // 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.
2545 mPreserveCursorPosition = preserveCursorPosition;
2547 // Reset incase we are in a pre-edit state.
2548 ImfManager imfManager = ImfManager::Get();
2551 imfManager.Reset(); // Will trigger a commit message
2555 void TextInput::CursorUpdate()
2559 ImfManager imfManager = ImfManager::Get();
2562 std::string text( GetText() );
2563 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2564 imfManager.SetCursorPosition ( mCursorPosition );
2565 imfManager.NotifyCursorPosition();
2569 /* Delete highlighted characters redisplay*/
2570 void TextInput::DeleteHighlightedText( bool inheritStyle )
2572 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2574 if(mHighlightMeshActor)
2576 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2578 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2579 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2581 // Get the styled text of the characters to be deleted as it may be needed if
2582 // the "exceed the text-input's boundaries" option is disabled.
2583 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2585 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2587 mStyledText.erase( start, end ); // erase range of characters
2589 // Remove text from TextView.
2591 if( mStyledText.empty() )
2593 // Styled text is empty, so set the placeholder text.
2594 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2595 mPlaceHolderSet = true;
2599 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2601 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2603 // It may happen than after removing a white space or a new line character,
2604 // two words merge, this new word could be big enough to not fit in its
2605 // current line, so moved to the next one, and make some part of the text to
2606 // exceed the text-input's boundary.
2607 if( !mExceedEnabled )
2609 // Get the new text layout after removing some characters.
2610 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2612 // Get text-input's size.
2613 const Vector3& size = GetControlSize();
2615 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2616 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2618 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2620 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2621 styledCharactersToDelete.begin(),
2622 styledCharactersToDelete.end() );
2626 GetTextLayoutInfo();
2632 const TextStyle oldInputStyle( mInputStyle );
2634 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2636 if( oldInputStyle != mInputStyle )
2638 // Updates the line height accordingly with the input style.
2641 EmitStyleChangedSignal();
2647 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2649 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2650 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2652 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2655 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2657 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2658 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2660 mStyledText.erase(itStart, itEnd);
2662 // update the selection handles if they are visible.
2663 if( mHighlightMeshActor )
2665 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2666 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2668 if( minHandle >= start + ncharacters )
2670 minHandle -= ncharacters;
2672 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2677 if( maxHandle >= start + ncharacters )
2679 maxHandle -= ncharacters;
2681 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2687 // 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.
2690 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2692 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2693 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2694 // Mean we do not re-draw the text more than we have too.
2697 /* Delete character at current cursor position and redisplay*/
2698 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2700 // Ensure positionToDelete is not out of bounds.
2701 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2702 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2703 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2705 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2708 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2710 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2712 // Get the styled text of the character to be deleted as it may be needed if
2713 // the "exceed the text-input's boundaries" option is disabled.
2714 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2716 mStyledText.erase(it); // erase the character left of positionToDelete
2718 if( mStyledText.empty() )
2720 // Styled text is empty, so set the placeholder text.
2721 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2722 mPlaceHolderSet = true;
2726 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2728 const Character characterToDelete = styledCharacterToDelete.mText[0];
2730 // It may happen than after removing a white space or a new line character,
2731 // two words merge, this new word could be big enough to not fit in its
2732 // current line, so moved to the next one, and make some part of the text to
2733 // exceed the text-input's boundary.
2734 if( !mExceedEnabled )
2736 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2738 // Get the new text layout after removing one character.
2739 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2741 // Get text-input's size.
2742 const Vector3& size = GetControlSize();
2744 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2745 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2747 MarkupProcessor::StyledTextArray array;
2748 array.push_back( styledCharacterToDelete );
2749 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2751 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2756 GetTextLayoutInfo();
2758 ShowGrabHandleAndSetVisibility( false );
2760 mCursorPosition = positionToDelete -1;
2762 const TextStyle oldInputStyle( mInputStyle );
2764 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2766 if( oldInputStyle != mInputStyle )
2768 // Updates the line height accordingly with the input style.
2771 EmitStyleChangedSignal();
2776 /*Insert new character into the string and (optionally) redisplay text-input*/
2777 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2779 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2781 // Ensure insertionPosition is not out of bounds.
2782 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2784 bool textExceedsMaximunNumberOfCharacters = false;
2785 bool textExceedsBoundary = false;
2786 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2788 ShowGrabHandleAndSetVisibility( false );
2790 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2794 mIgnoreCommitFlag = true;
2795 mPreEditFlag = false;
2796 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2797 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2800 if( textExceedsMaximunNumberOfCharacters )
2802 EmitMaxInputCharactersReachedSignal();
2805 if( textExceedsBoundary )
2807 EmitInputTextExceedsBoundariesSignal();
2808 PreEditReset( false );
2812 return insertedStringLength;
2815 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2821 cursor = ImageActor::New( cursorImage );
2825 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2828 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2829 cursor.SetNinePatchBorder( border );
2831 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2832 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2833 cursor.SetVisible(false);
2838 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2840 // As cursor is not moving due to grab handle, handle should be hidden.
2841 ShowGrabHandleAndSetVisibility( false );
2843 bool cursorPositionChanged = false;
2846 if ( mCursorPosition >= places )
2848 mCursorPosition = mCursorPosition - places;
2849 cursorPositionChanged = true;
2854 if ((mCursorPosition + places) <= mStyledText.size())
2856 mCursorPosition = mCursorPosition + places;
2857 cursorPositionChanged = true;
2861 if( cursorPositionChanged )
2863 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2865 const TextStyle oldInputStyle( mInputStyle );
2866 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2870 if( oldInputStyle != mInputStyle )
2872 // Updates the line height accordingly with the input style.
2875 EmitStyleChangedSignal();
2878 ImfManager imfManager = ImfManager::Get();
2881 imfManager.SetCursorPosition ( mCursorPosition );
2882 imfManager.NotifyCursorPosition();
2887 void TextInput::DrawCursor(const std::size_t nthChar)
2889 // Get height of cursor and set its size
2890 Size size( CURSOR_THICKNESS, 0.0f );
2891 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2893 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2897 // Measure Font so know how big text will be if no initial text to measure.
2898 size.height = mLineHeight;
2901 mCursor.SetSize(size);
2903 // If the character is italic then the cursor also tilts.
2904 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2906 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2908 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2910 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2911 bool altPositionValid; // Alternate cursor validity flag.
2912 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2913 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2915 SetAltCursorEnabled( altPositionValid );
2917 if(!altPositionValid)
2919 mCursor.SetPosition( position + UI_OFFSET );
2923 size.height *= 0.5f;
2924 mCursor.SetSize(size);
2925 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2927 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2928 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2929 size.height = rowSize.height * 0.5f;
2930 mCursorRTL.SetSize(size);
2931 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2934 if( IsScrollEnabled() )
2936 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2937 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2942 void TextInput::SetAltCursorEnabled( bool enabled )
2944 mCursorRTLEnabled = enabled;
2945 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2948 void TextInput::SetCursorVisibility( bool visible )
2950 mCursorVisibility = visible;
2951 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2952 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2955 void TextInput::CreateGrabHandle( Dali::Image image )
2961 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2965 mGrabHandleImage = image;
2968 mGrabHandle = ImageActor::New(mGrabHandleImage);
2969 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2970 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2972 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2974 ShowGrabHandleAndSetVisibility( false );
2976 CreateGrabArea( mGrabHandle );
2978 mActiveLayer.Add(mGrabHandle);
2982 void TextInput::CreateGrabArea( Actor& parent )
2984 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2985 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2986 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2987 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2988 mTapDetector.Attach( mGrabArea );
2989 mPanGestureDetector.Attach( mGrabArea );
2991 parent.Add(mGrabArea);
2994 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2996 Vector3 actualHandlePosition;
3000 mActualGrabHandlePosition.x += displacement.x;
3001 mActualGrabHandlePosition.y += displacement.y;
3003 // Grab handle should jump to the nearest character and take cursor with it
3004 std::size_t newCursorPosition = 0;
3005 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3007 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
3009 bool handleVisible = true;
3011 if( IsScrollEnabled() )
3013 const Vector3 controlSize = GetControlSize();
3014 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
3015 // Scrolls the text if the handle is not in a visible position
3016 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3023 mCurrentHandlePosition = actualHandlePosition;
3024 mScrollDisplacement = Vector2::ZERO;
3028 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3030 mScrollDisplacement.x = -SCROLL_SPEED;
3032 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3034 mScrollDisplacement.x = SCROLL_SPEED;
3036 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3038 mScrollDisplacement.y = -SCROLL_SPEED;
3040 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3042 mScrollDisplacement.y = SCROLL_SPEED;
3048 if( handleVisible && // Only redraw cursor and do updates if position changed
3049 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3051 mCursorPosition = newCursorPosition;
3053 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3055 const TextStyle oldInputStyle( mInputStyle );
3057 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3059 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3061 if( oldInputStyle != mInputStyle )
3063 // Updates the line height accordingly with the input style.
3066 EmitStyleChangedSignal();
3071 return actualHandlePosition;
3074 void TextInput::ShowGrabHandle( bool visible )
3076 if ( IsGrabHandleEnabled() )
3080 mGrabHandle.SetVisible( mGrabHandleVisibility );
3082 StartMonitoringStageForTouch();
3086 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3088 mGrabHandleVisibility = visible;
3089 ShowGrabHandle( visible );
3092 // Callbacks connected to be Property notifications for Boundary checking.
3094 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3096 mIsSelectionHandleOneFlipped = true;
3097 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3098 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3101 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3103 mIsSelectionHandleOneFlipped = false;
3104 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3105 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3108 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3110 mIsSelectionHandleTwoFlipped = true;
3111 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3112 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3115 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3117 mIsSelectionHandleTwoFlipped = false;
3118 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3119 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3122 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3123 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3125 mSelectionHandleOne.SetOpacity(0.0f);
3128 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3130 mSelectionHandleOne.SetOpacity(1.0f);
3133 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3135 mSelectionHandleTwo.SetOpacity(0.0f);
3138 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3140 mSelectionHandleTwo.SetOpacity(1.0f);
3143 // End of Callbacks connected to be Property notifications for Boundary checking.
3145 void TextInput::SetUpHandlePropertyNotifications()
3147 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3149 Vector3 handlesize = GetSelectionHandleSize();
3151 // Exceeding horizontal boundary
3152 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3153 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3155 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3156 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3158 // Within horizontal boundary
3159 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3160 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3162 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3163 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3165 // Exceeding vertical boundary
3166 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3167 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3168 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3169 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3171 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3172 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3173 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3174 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3176 // Within vertical boundary
3177 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3178 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3179 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3180 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3182 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3183 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3184 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3185 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3188 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3190 mSelectionHandleOnePosition = start;
3191 mSelectionHandleTwoPosition = end;
3193 if ( !mSelectionHandleOne )
3195 // create normal and pressed images
3196 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3197 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3199 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3200 mSelectionHandleOne.SetName("SelectionHandleOne");
3201 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3202 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3203 mIsSelectionHandleOneFlipped = false;
3204 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3206 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3207 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3209 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3210 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3212 mTapDetector.Attach( mHandleOneGrabArea );
3213 mPanGestureDetector.Attach( mHandleOneGrabArea );
3215 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3217 mSelectionHandleOne.Add( mHandleOneGrabArea );
3218 mActiveLayer.Add( mSelectionHandleOne );
3221 if ( !mSelectionHandleTwo )
3223 // create normal and pressed images
3224 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3225 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3227 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3228 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3229 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3230 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3231 mIsSelectionHandleTwoFlipped = false;
3232 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3234 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3235 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3236 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3237 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3239 mTapDetector.Attach( mHandleTwoGrabArea );
3240 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3242 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3244 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3246 mActiveLayer.Add( mSelectionHandleTwo );
3249 SetUpHandlePropertyNotifications();
3251 // update table as text may have changed.
3252 GetTextLayoutInfo();
3254 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3255 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3257 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3258 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3260 // Calculates and set the visibility if the scroll mode is enabled.
3261 bool isSelectionHandleOneVisible = true;
3262 bool isSelectionHandleTwoVisible = true;
3263 if( IsScrollEnabled() )
3265 const Vector3& controlSize( GetControlSize() );
3266 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3267 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3268 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3269 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3272 CreateHighlight(); // function will only create highlight if not already created.
3275 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3277 Vector3 actualHandlePosition;
3279 if ( mSelectionHandleOne && mSelectionHandleTwo )
3281 const Vector3& controlSize = GetControlSize();
3283 Size cursorSize( CURSOR_THICKNESS, 0.f );
3285 // Get a reference of the wanted selection handle (handle one or two).
3286 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3288 // Get a reference for the current position of the handle and a copy of its pair
3289 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3290 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3292 // Get a handle of the selection handle actor
3293 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3295 // Selection handles should jump to the nearest character
3296 std::size_t newHandlePosition = 0;
3297 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3299 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3301 bool handleVisible = true;
3303 if( IsScrollEnabled() )
3305 mCurrentSelectionId = handleId;
3307 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3308 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3309 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3316 mCurrentSelectionHandlePosition = actualHandlePosition;
3317 mScrollDisplacement = Vector2::ZERO;
3321 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3323 mScrollDisplacement.x = -SCROLL_SPEED;
3325 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3327 mScrollDisplacement.x = SCROLL_SPEED;
3329 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3331 mScrollDisplacement.y = -SCROLL_SPEED;
3333 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3335 mScrollDisplacement.y = SCROLL_SPEED;
3341 if ( handleVisible && // Ensure the handle is visible.
3342 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3343 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3345 currentSelectionHandlePosition = newHandlePosition;
3347 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3348 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3352 if ( handleId == HandleOne )
3354 const TextStyle oldInputStyle( mInputStyle );
3356 // Set Active Style to that of first character in selection
3357 if( mSelectionHandleOnePosition < mStyledText.size() )
3359 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3362 if( oldInputStyle != mInputStyle )
3364 // Updates the line height accordingly with the input style.
3367 EmitStyleChangedSignal();
3373 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3376 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3379 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3380 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3382 if ( selectionHandleActor )
3384 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3385 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3386 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3388 if( IsScrollEnabled() )
3390 const Size cursorSize( CURSOR_THICKNESS,
3391 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3392 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3394 GetControlSize() ) );
3399 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3401 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3402 // For now the visual position of end of logical string will be end of visual string.
3403 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3405 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3408 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3410 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3411 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3412 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3413 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3415 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3417 // Deselect text prior to startSelectionIt
3418 for(;it!=startSelectionIt;++it)
3420 selectedVisualText[*it] = false;
3423 // Select text from startSelectionIt -> endSelectionIt
3424 for(;it!=endSelectionIt;++it)
3426 selectedVisualText[*it] = true;
3429 // Deselect text after endSelection
3432 selectedVisualText[*it] = false;
3435 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3438 // Calculate the dimensions of the quads they will make the highlight mesh
3439 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3441 // At the moment there is no public API to modify the block alignment option.
3442 const bool blockAlignEnabled = true;
3444 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3446 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3448 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3449 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3451 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3452 std::vector<bool> selectedVisualText;
3453 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3454 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3455 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3457 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3458 float rowLeft = 0.0f;
3459 float rowRight = 0.0f;
3460 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3461 float maxRowLeft = std::numeric_limits<float>::max();
3462 float maxRowRight = 0.0f;
3464 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3466 // Scan through entire text.
3469 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3471 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3472 bool charSelected( false );
3473 if( selectedIt != selectedEndIt )
3475 charSelected = *selectedIt++;
3478 if(selectionState == SelectionNone)
3482 selectionState = SelectionStarted;
3483 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3484 rowRight = rowLeft + charInfo.mSize.width;
3487 else if(selectionState == SelectionStarted)
3489 // break selection on:
3490 // 1. new line causing selection break. (\n or wordwrap)
3491 // 2. character not selected.
3492 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3495 // finished selection.
3496 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3497 // that it resides on. That way this enumeration is not necessary.
3499 if(lastIt->mIsNewLineChar)
3501 // 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.
3502 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3504 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3505 maxRowLeft = std::min(maxRowLeft, min.x);
3506 maxRowRight = std::max(maxRowRight, max.x);
3507 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3508 float rowTop = rowBottom - rowSize.height;
3510 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3511 if(charSelected && blockAlignEnabled)
3513 rowRight = std::numeric_limits<float>::max();
3515 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3517 selectionState = SelectionNone;
3519 // Still selected? start a new selection
3522 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3523 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3524 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3525 selectionState = SelectionStarted;
3530 // build up highlight(s) with this selection data.
3531 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3532 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3539 // If reached end, and still on selection, then close selection.
3542 if(selectionState == SelectionStarted)
3544 // finished selection.
3546 if(lastIt->mIsNewLineChar)
3548 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3550 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3551 maxRowLeft = std::min(maxRowLeft, min.x);
3552 maxRowRight = std::max(maxRowRight, max.x);
3553 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3554 float rowTop = rowBottom - rowSize.height;
3555 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3559 // Get the top left and bottom right corners.
3560 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3561 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3562 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3564 // Clamp quads so they appear to clip to borders of the whole text.
3565 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3567 // For block-align align Further Clamp quads to max left and right extents
3568 if(blockAlignEnabled)
3570 // BlockAlign: Will adjust highlight to block:
3572 // H[ello] (top row right = max of all rows right)
3573 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3574 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3575 // [text] (bottom row left = min of all rows left)
3576 // (common in SMS messaging selection)
3578 // As opposed to the default which is tight text highlighting.
3583 // (common in regular text editors/web browser selection)
3585 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3588 // Finally clamp quads again so they don't exceed the boundry of the control.
3589 const Vector3& controlSize = GetControlSize();
3590 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3593 return mNewHighlightInfo;
3596 void TextInput::UpdateHighlight()
3598 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3600 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3602 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3603 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3604 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3605 // [BOTTOM] [ MIDDLE ]
3608 // Each quad is created as 2 triangles.
3609 // Middle is just 1 quad regardless of its size.
3623 if ( mHighlightMeshActor )
3625 // vertex and triangle buffers should always be present if MeshActor is alive.
3626 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3627 MeshData::VertexContainer vertices;
3628 Dali::MeshData::FaceIndices faceIndices;
3630 if( !newHighlightInfo.mQuadList.empty() )
3632 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3633 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3635 // vertex position defaults to (0 0 0)
3636 MeshData::Vertex vertex;
3637 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3640 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3642 // Add each quad geometry (a sub-selection) to the mesh data.
3652 QuadCoordinates& quad = *iter;
3654 vertex.x = quad.min.x;
3655 vertex.y = quad.min.y;
3656 vertices.push_back( vertex );
3659 vertex.x = quad.max.x;
3660 vertex.y = quad.min.y;
3661 vertices.push_back( vertex );
3663 // bottom-left (v+2)
3664 vertex.x = quad.min.x;
3665 vertex.y = quad.max.y;
3666 vertices.push_back( vertex );
3668 // bottom-right (v+3)
3669 vertex.x = quad.max.x;
3670 vertex.y = quad.max.y;
3671 vertices.push_back( vertex );
3673 // triangle A (3, 1, 0)
3674 faceIndices.push_back( v + 3 );
3675 faceIndices.push_back( v + 1 );
3676 faceIndices.push_back( v );
3678 // triangle B (0, 2, 3)
3679 faceIndices.push_back( v );
3680 faceIndices.push_back( v + 2 );
3681 faceIndices.push_back( v + 3 );
3683 mMeshData.SetFaceIndices( faceIndices );
3686 BoneContainer bones(0); // passed empty as bones not required
3687 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3688 mHighlightMesh.UpdateMeshData(mMeshData);
3693 void TextInput::ClearPopup()
3695 mPopUpPanel.Clear();
3698 void TextInput::AddPopupOptions()
3700 mPopUpPanel.AddPopupOptions();
3703 void TextInput::SetPopupPosition(const Vector3& position)
3705 mPopUpPanel.Self().SetPosition( position );
3708 void TextInput::HidePopup(bool animate, bool signalFinished )
3710 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3712 mPopUpPanel.Hide( animate );
3714 if( animate && signalFinished )
3716 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3721 void TextInput::ShowPopup(bool animate)
3725 if(mHighlightMeshActor && mState == StateEdit)
3729 // When text is selected, show popup above top handle (and text), or below bottom handle.
3730 // topHandle: referring to the top most point of the handle or the top line of selection.
3731 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3733 topHandle = mSelectionHandleOneActualPosition;
3734 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3738 topHandle = mSelectionHandleTwoActualPosition;
3739 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3741 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3742 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3744 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3745 Vector3 bottomHandle;
3746 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3747 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3748 mPopUpPanel.SetAlternativeOffset(Vector2( mBoundingRectangleWorldCoordinates.x, bottomHandle.y - topHandle.y));
3752 // When no text is selected, show popup at world position of grab handle or cursor
3753 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3754 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3755 position.y -= rowSize.height;
3756 // if can't be positioned above, then position below row.
3757 Vector2 alternativePopUpPosition( mBoundingRectangleWorldCoordinates.x, position.y ); // default if no grab handle
3760 // If grab handle enabled then position pop-up below the grab handle.
3761 alternativePopUpPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + BOTTOM_HANDLE_BOTTOM_OFFSET ;
3763 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3766 // reposition popup above the desired cursor posiiton.
3767 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3768 textViewSize.z = 0.0f;
3769 // World position = world position of local position i.e. top-left corner of TextView
3770 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3772 SetPopupPosition( worldPosition );
3775 mPopUpPanel.Show(animate);
3776 StartMonitoringStageForTouch();
3778 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3781 void TextInput::ShowPopupCutCopyPaste()
3785 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3786 // Check the selected text is whole text or not.
3787 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3789 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3792 if ( !mStyledText.empty() )
3795 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCopy, true );
3796 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3799 if( mClipboard && mClipboard.NumberOfItems() )
3801 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3802 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3807 mPopUpPanel.Hide(false);
3811 void TextInput::SetUpPopUpSelection()
3814 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3815 // If no text exists then don't offer to select
3816 if ( !mStyledText.empty() )
3818 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3819 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelect, true );
3820 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3822 // if clipboard has valid contents then offer paste option
3823 if( mClipboard && mClipboard.NumberOfItems() )
3825 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3826 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3831 mPopUpPanel.Hide(false);
3834 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3839 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3840 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3841 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3842 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3844 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3846 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3848 float closestYdifference = std::numeric_limits<float>::max();
3849 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3850 std::size_t numberOfMatchedCharacters = 0;
3852 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3853 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3855 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3857 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3858 float baselinePosition = info.mPosition.y - info.mDescender;
3860 if( info.mIsVisible )
3862 // store difference between source y point and the y position of the current character
3863 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3865 if( currentYdifference < closestYdifference )
3867 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3868 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3869 closestYdifference = currentYdifference;
3870 matchedCharacters.clear();
3871 numberOfMatchedCharacters = 0; // reset count
3874 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3875 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3877 // ignore new line character.
3878 if( !info.mIsNewLineChar )
3880 matchedCharacters.push_back( info );
3881 numberOfMatchedCharacters++;
3885 } // End of loop checking each character's y position in the character layout table
3887 // Check if last character is a newline, if it is
3888 // then need pretend there is an imaginary line afterwards,
3889 // and check if user is touching below previous line.
3890 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3892 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3894 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3898 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3899 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3901 bool matched( false );
3903 // 2 Iterate through matching list of y positions and find closest matching X position.
3904 for( ; it != endIt; ++it )
3906 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3908 if( info.mIsVisible )
3910 // stop when on left side of character's center.
3911 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3912 if( sourceScrollOffset.x < characterMidPointPosition )
3914 if(info.mIsRightToLeftCharacter)
3916 rightToLeftChar = true;
3918 glyphIntersection = info.mPosition.x;
3923 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3929 rightToLeftChar = lastRightToLeftChar;
3932 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3933 closestIndex = lineOffset + matchCharacterIndex;
3935 mClosestCursorPositionEOL = false; // reset
3936 if ( it == endIt && !matched )
3938 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3941 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3942 if( rightToLeftChar && lastRightToLeftChar )
3944 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3949 // closestIndex is the visual index, need to convert it to the logical index
3950 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3952 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3954 // Checks for situations where user is touching between LTR and RTL
3955 // characters. To identify if the user means the end of a LTR string
3956 // or the beginning of an RTL string, and vice versa.
3957 if( closestIndex > 0 )
3959 if( rightToLeftChar && !lastRightToLeftChar )
3964 // A: In this touch range, the user is indicating that they wish to place
3965 // the cursor at the end of the LTR text.
3966 // B: In this touch range, the user is indicating that they wish to place
3967 // the cursor at the end of the RTL text.
3969 // Result of touching A area:
3970 // [.....LTR]|[RTL......]+
3972 // |: primary cursor (for typing LTR chars)
3973 // +: secondary cursor (for typing RTL chars)
3975 // Result of touching B area:
3976 // [.....LTR]+[RTL......]|
3978 // |: primary cursor (for typing RTL chars)
3979 // +: secondary cursor (for typing LTR chars)
3981 if( sourceScrollOffset.x < glyphIntersection )
3986 else if( !rightToLeftChar && lastRightToLeftChar )
3988 if( sourceScrollOffset.x < glyphIntersection )
3995 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3996 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3997 // one further ahead
3998 if( rightToLeftChar && !lastRightToLeftChar )
4003 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
4005 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
4007 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
4016 float TextInput::GetLineJustificationPosition() const
4018 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4019 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4020 float alignmentOffset = 0.f;
4022 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4023 if( alignment & Toolkit::Alignment::HorizontalLeft )
4025 alignmentOffset = 0.f;
4027 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4029 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4031 else if( alignment & Toolkit::Alignment::HorizontalRight )
4033 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4036 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4037 float justificationOffset = 0.f;
4039 switch( justification )
4041 case Toolkit::TextView::Left:
4043 justificationOffset = 0.f;
4046 case Toolkit::TextView::Center:
4048 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4051 case Toolkit::TextView::Right:
4053 justificationOffset = mTextLayoutInfo.mTextSize.width;
4056 case Toolkit::TextView::Justified:
4058 justificationOffset = 0.f;
4063 DALI_ASSERT_ALWAYS( false );
4067 return alignmentOffset + justificationOffset;
4070 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4072 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4073 A newline character is not inserted in this case */
4075 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4077 Vector3 cursorPosition;
4079 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4081 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4083 // end character so use
4084 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4085 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4089 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4092 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4094 // If previous character on a different line then use current characters position
4095 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4097 if ( mClosestCursorPositionEOL )
4099 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4103 cursorPosition = Vector3(currentCharInfo.mPosition);
4108 // Previous character is on same line so use position of previous character plus it's width.
4109 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4112 return cursorPosition;
4115 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4117 bool direction(false);
4118 Vector3 alternatePosition;
4119 bool alternatePositionValid(false);
4121 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4124 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4126 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4128 alternatePositionValid = false;
4129 directionRTL = false;
4131 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4133 std::size_t visualCharacterPosition;
4135 // When cursor is not at beginning, consider possibility of
4136 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4137 if(characterPosition > 0)
4139 // Cursor position should be the end of the last character.
4140 // If the last character is LTR, then the end is on the right side of the glyph.
4141 // If the last character is RTL, then the end is on the left side of the glyph.
4142 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4144 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4146 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4149 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4150 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4152 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4153 const Vector3& size = GetControlSize();
4155 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4157 --visualCharacterPosition;
4159 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4162 if(!info.mIsNewLineChar)
4164 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4168 // When cursor points to first character on new line, position cursor at the start of this glyph.
4169 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4171 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4172 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4173 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4175 cursorPosition.x = infoNext.mPosition.x + start;
4176 cursorPosition.y = infoNext.mPosition.y;
4180 // If cursor points to the end of text, then can only position
4181 // cursor where the new line starts based on the line-justification position.
4182 cursorPosition.x = GetLineJustificationPosition();
4184 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4186 // If this is after the last character, then we can assume that the new cursor
4187 // should be exactly one row below the current row.
4189 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4190 cursorPosition.y = info.mPosition.y + rowRect.height;
4194 // If this is not after last character, then we can use this row's height.
4195 // should be exactly one row below the current row.
4197 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4198 cursorPosition.y = info.mPosition.y + rowRect.height;
4203 directionRTL = info.mIsRightToLeftCharacter;
4205 // 1. When the cursor is neither at the beginning or the end,
4206 // we can show multiple cursors under situations when the cursor is
4207 // between RTL and LTR text...
4208 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4210 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4212 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4213 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4215 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4217 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4218 // Text: [...LTR...]|[...RTL...]
4220 // Alternate cursor pos: ^
4221 // In which case we need to display an alternate cursor for the RTL text.
4223 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4224 alternatePosition.y = infoAlt.mPosition.y;
4225 alternatePositionValid = true;
4227 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4229 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4230 // Text: |[...RTL...] [...LTR....]
4232 // Alternate cursor pos: ^
4233 // In which case we need to display an alternate cursor for the RTL text.
4235 alternatePosition.x = infoAlt.mPosition.x;
4236 alternatePosition.y = infoAlt.mPosition.y;
4237 alternatePositionValid = true;
4242 // 2. When the cursor is at the end of the text,
4243 // and we have multi-directional text,
4244 // we can also consider showing mulitple cursors.
4245 // The rule here is:
4246 // If first and last characters on row are different
4247 // Directions, then two cursors need to be displayed.
4249 // Get first logical glyph on row
4250 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4252 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4253 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4255 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4257 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4258 // Text: [...LTR...]|[...RTL...]
4260 // Alternate cursor pos: ^
4261 // In which case we need to display an alternate cursor for the RTL text, this cursor
4262 // should be at the end of the given line.
4264 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4265 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4266 alternatePosition.y = infoAlt.mPosition.y;
4267 alternatePositionValid = true;
4269 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4271 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4272 // Text: |[...RTL...] [...LTR....]
4274 // Alternate cursor pos: ^
4275 // In which case we need to display an alternate cursor for the RTL text.
4277 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4278 alternatePosition.x = infoAlt.mPosition.x;
4279 alternatePosition.y = infoAlt.mPosition.y;
4280 alternatePositionValid = true;
4283 } // characterPosition > 0
4284 else if(characterPosition == 0)
4286 // When the cursor position is at the beginning, it should be at the start of the current character.
4287 // If the current character is LTR, then the start is on the right side of the glyph.
4288 // If the current character is RTL, then the start is on the left side of the glyph.
4289 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4291 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4293 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4296 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4297 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4299 cursorPosition.x = info.mPosition.x + start;
4300 cursorPosition.y = info.mPosition.y;
4301 directionRTL = info.mIsRightToLeftCharacter;
4306 // If the character table is void, place the cursor accordingly the text alignment.
4307 const Vector3& size = GetControlSize();
4309 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4310 float alignmentOffset = 0.f;
4312 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4313 if( alignment & Toolkit::Alignment::HorizontalLeft )
4315 alignmentOffset = 0.f;
4317 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4319 alignmentOffset = 0.5f * ( size.width );
4321 else if( alignment & Toolkit::Alignment::HorizontalRight )
4323 alignmentOffset = size.width;
4326 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4327 cursorPosition.x = alignmentOffset;
4329 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4330 if( alignment & Toolkit::Alignment::VerticalTop )
4332 cursorPosition.y = mLineHeight;
4334 else if( alignment & Toolkit::Alignment::VerticalCenter )
4336 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4338 else if( alignment & Toolkit::Alignment::VerticalBottom )
4340 cursorPosition.y = size.height;
4344 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4345 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4346 if( alternatePositionValid )
4348 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4349 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4352 return cursorPosition;
4355 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4357 // scan string from current position to beginning of current line to note direction of line
4358 while(logicalPosition)
4361 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4362 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4369 return logicalPosition;
4372 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4376 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4379 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4381 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4382 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4384 min = Vector2::ZERO;
4385 max = Vector2(0.0f, mLineHeight);
4389 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4390 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4391 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4393 // If cursor is pointing to end of line, then start from last character.
4394 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4396 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4398 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4399 if( !it->mIsVisible )
4401 characterPosition = FindVisibleCharacter( Left, characterPosition );
4402 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4405 // Scan characters left and right of cursor, stopping when end of line/string reached or
4406 // y position greater than threshold of reference line.
4408 // 1. scan left until we reach the beginning or a different line.
4409 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4410 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4411 // min-x position is the left-most char's left (x)
4412 // max-x position is the right-most char's right (x)
4413 // min-y position is the minimum of all character's top (y)
4414 // max-y position is the maximum of all character's bottom (y+height)
4415 min.y = validCharIt->mPosition.y;
4416 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4421 min.y = std::min(min.y, validCharIt->mPosition.y);
4422 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4431 if( (it->mPosition.y < referenceLine) ||
4432 (it->mIsNewLineChar) ||
4439 // info refers to the first character on this line.
4440 min.x = validCharIt->mPosition.x;
4442 // 2. scan right until we reach end or a different line
4443 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4444 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4448 if( (it->mPosition.y > referenceLine) ||
4449 (it->mIsNewLineChar) ||
4456 min.y = std::min(min.y, validCharIt->mPosition.y);
4457 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4462 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4464 if ( validCharIt != end )
4466 // info refers to the last character on this line.
4467 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4470 return Size( max.x - min.x, max.y - min.y );
4473 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4475 Actor popUpPanel = mPopUpPanel.GetRootActor();
4477 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4483 Dali::Actor parent( touchedActor.GetParent() );
4487 return WasTouchedCheck( parent );
4494 void TextInput::StartMonitoringStageForTouch()
4496 Stage stage = Stage::GetCurrent();
4497 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4500 void TextInput::EndMonitoringStageForTouch()
4502 Stage stage = Stage::GetCurrent();
4503 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4506 void TextInput::OnStageTouched(const TouchEvent& event)
4508 if( event.GetPointCount() > 0 )
4510 if ( TouchPoint::Down == event.GetPoint(0).state )
4512 const Actor touchedActor(event.GetPoint(0).hitActor);
4514 bool popUpShown( false );
4516 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4521 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4523 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4525 EndMonitoringStageForTouch();
4526 HidePopup( true, false );
4529 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4531 EndMonitoringStageForTouch();
4532 ShowGrabHandleAndSetVisibility( false );
4538 void TextInput::SelectText(std::size_t start, std::size_t end)
4540 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4541 IsGrabHandleEnabled()?"true":"false",
4542 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4543 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4544 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4546 StartMonitoringStageForTouch();
4548 if ( mEditModeActive ) // Only allow text selection when in edit mode
4550 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4551 mSelectingText = true;
4553 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4555 ImfManager imfManager = ImfManager::Get();
4558 imfManager.SetCursorPosition ( mCursorPosition );
4559 imfManager.SetSurroundingText( GetText() );
4560 imfManager.NotifyCursorPosition();
4562 // 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.
4564 // Hide grab handle when selecting.
4565 ShowGrabHandleAndSetVisibility( false );
4567 if( start != end ) // something to select
4569 SetCursorVisibility( false );
4570 StopCursorBlinkTimer();
4572 CreateSelectionHandles(start, end);
4575 const TextStyle oldInputStyle( mInputStyle );
4576 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4578 if( oldInputStyle != mInputStyle )
4580 // Updates the line height accordingly with the input style.
4583 EmitStyleChangedSignal();
4589 mSelectingText = false;
4593 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4595 MarkupProcessor::StyledTextArray currentSelectedText;
4597 if ( IsTextSelected() )
4599 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4600 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4602 for(; it != end; ++it)
4604 MarkupProcessor::StyledText& styledText( *it );
4605 currentSelectedText.push_back( styledText );
4608 return currentSelectedText;
4611 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4613 const std::size_t beginIndex = std::min( begin, end );
4614 const std::size_t endIndex = std::max( begin, end );
4617 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4619 // Create a styled text array used to replace the text into the text-view.
4620 MarkupProcessor::StyledTextArray text;
4621 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4623 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4624 GetTextLayoutInfo();
4626 if( IsScrollEnabled() )
4628 // Need to set the scroll position as the text's size may have changed.
4629 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4632 ShowGrabHandleAndSetVisibility( false );
4638 // Set Handle positioning as the new style may have repositioned the characters.
4639 SetSelectionHandlePosition(HandleOne);
4640 SetSelectionHandlePosition(HandleTwo);
4643 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4645 // Just hide the grab handle when keyboard is hidden.
4646 if (!keyboardShown )
4648 ShowGrabHandleAndSetVisibility( false );
4650 // If the keyboard is not now being shown, then hide the popup panel
4651 mPopUpPanel.Hide( true );
4655 // Removes highlight and resumes edit mode state
4656 void TextInput::RemoveHighlight()
4658 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4660 if ( mHighlightMeshActor )
4662 if ( mSelectionHandleOne )
4664 mActiveLayer.Remove( mSelectionHandleOne );
4665 mSelectionHandleOne.Reset();
4666 mSelectionHandleOneOffset.x = 0.0f;
4668 if ( mSelectionHandleTwo )
4670 mActiveLayer.Remove( mSelectionHandleTwo );
4671 mSelectionHandleTwo.Reset();
4672 mSelectionHandleTwoOffset.x = 0.0f;
4675 mNewHighlightInfo.mQuadList.clear();
4677 Self().Remove( mHighlightMeshActor );
4679 SetCursorVisibility( true );
4680 StartCursorBlinkTimer();
4682 mHighlightMeshActor.Reset();
4683 // NOTE: We cannot dereference mHighlightMesh, due
4684 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4689 mSelectionHandleOnePosition = 0;
4690 mSelectionHandleTwoPosition = 0;
4693 void TextInput::CreateHighlight()
4695 if ( !mHighlightMeshActor )
4697 mMeshData = MeshData( );
4698 mMeshData.SetHasNormals( true );
4700 mCustomMaterial = Material::New("CustomMaterial");
4701 mCustomMaterial.SetDiffuseColor( mMaterialColor );
4703 mMeshData.SetMaterial( mCustomMaterial );
4705 mHighlightMesh = Mesh::New( mMeshData );
4707 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4708 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4709 mHighlightMeshActor.SetInheritShaderEffect( false );
4710 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4711 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4712 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4713 mHighlightMeshActor.SetAffectedByLighting(false);
4715 Self().Add(mHighlightMeshActor);
4720 bool TextInput::CopySelectedTextToClipboard()
4722 mCurrentCopySelecton.clear();
4724 mCurrentCopySelecton = GetSelectedText();
4726 std::string stringToStore;
4728 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4729 * a marked up string.
4731 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4732 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4733 bool success = mClipboard.SetItem( stringToStore );
4737 void TextInput::PasteText( const Text& text )
4739 // Update Flag, indicates whether to update the text-input contents or not.
4740 // Any key stroke that results in a visual change of the text-input should
4741 // set this flag to true.
4742 bool update = false;
4743 if( mHighlightMeshActor )
4745 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4746 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4748 ImfManager imfManager = ImfManager::Get();
4751 imfManager.SetCursorPosition( mCursorPosition );
4752 imfManager.NotifyCursorPosition();
4754 DeleteHighlightedText( true );
4758 bool textExceedsMaximunNumberOfCharacters = false;
4759 bool textExceedsBoundary = false;
4761 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4763 mCursorPosition += insertedStringLength;
4764 ImfManager imfManager = ImfManager::Get();
4767 imfManager.SetCursorPosition ( mCursorPosition );
4768 imfManager.NotifyCursorPosition();
4771 update = update || ( insertedStringLength > 0 );
4777 if( insertedStringLength < text.GetLength() )
4779 EmitMaxInputCharactersReachedSignal();
4782 if( textExceedsBoundary )
4784 EmitInputTextExceedsBoundariesSignal();
4788 void TextInput::SetTextDirection()
4790 // Put the cursor to the right if we are empty and an RTL language is being used.
4791 if ( mStyledText.empty() )
4793 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4795 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4796 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4798 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4799 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4801 int alignment( mDisplayedTextView.GetTextAlignment() &
4802 ( Toolkit::Alignment::VerticalTop |
4803 Toolkit::Alignment::VerticalCenter |
4804 Toolkit::Alignment::VerticalBottom |
4805 Toolkit::Alignment::HorizontalCenter ) );
4806 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4808 // If our alignment is in the center, then do not change.
4809 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4811 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4814 // If our justification is in the center, then do not change.
4815 if ( justification != Toolkit::TextView::Center )
4817 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4820 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4821 mDisplayedTextView.SetLineJustification( justification );
4825 void TextInput::UpdateLineHeight()
4827 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4828 mLineHeight = font.GetLineHeight();
4830 // 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.
4832 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4834 if( !mExceedEnabled || shrink )
4836 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4840 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4842 std::size_t position = 0;
4844 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4850 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4852 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4854 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4860 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4861 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4863 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4869 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4874 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4881 void TextInput::SetSortModifier( float depthOffset )
4883 if(mDisplayedTextView)
4885 mDisplayedTextView.SetSortModifier(depthOffset);
4889 void TextInput::SetSnapshotModeEnabled( bool enable )
4891 if(mDisplayedTextView)
4893 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4897 bool TextInput::IsSnapshotModeEnabled() const
4899 bool snapshotEnabled = false;
4901 if(mDisplayedTextView)
4903 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4906 return snapshotEnabled;
4909 void TextInput::SetMarkupProcessingEnabled( bool enable )
4911 mMarkUpEnabled = enable;
4914 bool TextInput::IsMarkupProcessingEnabled() const
4916 return mMarkUpEnabled;
4919 void TextInput::SetScrollEnabled( bool enable )
4921 if( mDisplayedTextView )
4923 mDisplayedTextView.SetScrollEnabled( enable );
4928 // Don't set cursor's and handle's visibility to false if they are outside the
4929 // boundaries of the text-input.
4930 mIsCursorInScrollArea = true;
4931 mIsGrabHandleInScrollArea = true;
4932 if( mSelectionHandleOne && mSelectionHandleTwo )
4934 mSelectionHandleOne.SetVisible( true );
4935 mSelectionHandleTwo.SetVisible( true );
4937 if( mHighlightMeshActor )
4939 mHighlightMeshActor.SetVisible( true );
4945 bool TextInput::IsScrollEnabled() const
4947 bool scrollEnabled = false;
4949 if( mDisplayedTextView )
4951 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4954 return scrollEnabled;
4957 void TextInput::SetScrollPosition( const Vector2& position )
4959 if( mDisplayedTextView )
4961 mDisplayedTextView.SetScrollPosition( position );
4965 Vector2 TextInput::GetScrollPosition() const
4967 Vector2 scrollPosition;
4969 if( mDisplayedTextView )
4971 scrollPosition = mDisplayedTextView.GetScrollPosition();
4974 return scrollPosition;
4977 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4979 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4980 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4981 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4983 // Add style to the new input text.
4984 MarkupProcessor::StyledTextArray textToInsert;
4985 for( std::size_t i = 0; i < insertedStringLength; ++i )
4987 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4988 textToInsert.push_back( newStyledCharacter );
4991 //Insert text to the TextView.
4992 const bool emptyTextView = mStyledText.empty();
4993 if( emptyTextView && mPlaceHolderSet )
4995 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4996 mDisplayedTextView.SetText( textToInsert );
5000 if( 0 == numberOfCharactersToReplace )
5002 mDisplayedTextView.InsertTextAt( position, textToInsert );
5006 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
5009 mPlaceHolderSet = false;
5011 if( textToInsert.empty() )
5013 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
5014 GetTextLayoutInfo();
5018 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
5019 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5022 textExceedsBoundary = false;
5024 if( !mExceedEnabled )
5026 const Vector3& size = GetControlSize();
5028 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5030 // If new text does not fit within TextView
5031 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5032 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5033 GetTextLayoutInfo();
5034 textExceedsBoundary = true;
5035 insertedStringLength = 0;
5038 if( textExceedsBoundary )
5040 // Add the part of the text which fits on the text-input.
5042 // Split the text which doesn't fit in two halves.
5043 MarkupProcessor::StyledTextArray firstHalf;
5044 MarkupProcessor::StyledTextArray secondHalf;
5045 SplitText( textToInsert, firstHalf, secondHalf );
5047 // Clear text. This text will be filled with the text inserted.
5048 textToInsert.clear();
5050 // Where to insert the text.
5051 std::size_t positionToInsert = position;
5053 bool end = text.GetLength() <= 1;
5056 // Insert text and check ...
5057 const std::size_t textLength = firstHalf.size();
5058 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5059 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5061 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5063 // Inserted text doesn't fit.
5065 // Remove inserted text
5066 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5067 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5069 // The iteration finishes when only one character doesn't fit.
5070 end = textLength <= 1;
5074 // Prepare next two halves for next iteration.
5075 MarkupProcessor::StyledTextArray copyText = firstHalf;
5076 SplitText( copyText, firstHalf, secondHalf );
5083 // store text to be inserted in mStyledText.
5084 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5086 // Increase the inserted characters counter.
5087 insertedStringLength += textLength;
5089 // Prepare next two halves for next iteration.
5090 MarkupProcessor::StyledTextArray copyText = secondHalf;
5091 SplitText( copyText, firstHalf, secondHalf );
5093 // Update where next text has to be inserted
5094 positionToInsert += textLength;
5100 if( textToInsert.empty() && emptyTextView )
5102 // No character has been added and the text-view was empty.
5103 // Set the placeholder text.
5104 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5105 mPlaceHolderSet = true;
5109 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5110 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5111 mPlaceHolderSet = false;
5114 return insertedStringLength;
5117 void TextInput::GetTextLayoutInfo()
5119 if( mStyledText.empty() )
5121 // The text-input has no text, clear the text-view's layout info.
5122 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5126 if( mDisplayedTextView )
5128 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5132 // There is no text-view.
5133 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5138 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5140 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5144 TextInput& textInputImpl( GetImpl( textInput ) );
5146 switch ( propertyIndex )
5148 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5150 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5153 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5155 textInputImpl.mPopUpPanel.SetCutPastePopUpColor( value.Get< Vector4 >() );
5158 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5160 textInputImpl.mPopUpPanel.SetCutPastePopUpPressedColor( value.Get< Vector4 >() );
5163 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5165 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5168 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5170 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5173 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5175 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5178 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5180 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5183 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5185 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5188 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5190 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5197 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5199 Property::Value value;
5201 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5205 TextInput& textInputImpl( GetImpl( textInput ) );
5207 switch ( propertyIndex )
5209 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5211 value = textInputImpl.GetMaterialDiffuseColor();
5214 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5216 value = textInputImpl.mPopUpPanel.GetCutPastePopUpColor();
5219 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5221 value = textInputImpl.mPopUpPanel.GetCutPastePopUpPressedColor();
5224 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5226 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5229 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5231 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5234 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5236 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5239 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5241 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5244 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5246 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5249 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5251 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5259 void TextInput::EmitStyleChangedSignal()
5261 // emit signal if input style changes.
5262 Toolkit::TextInput handle( GetOwner() );
5263 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5266 void TextInput::EmitTextModified()
5268 // emit signal when text changes.
5269 Toolkit::TextInput handle( GetOwner() );
5270 mTextModifiedSignal.Emit( handle );
5274 void TextInput::EmitMaxInputCharactersReachedSignal()
5276 // emit signal if max characters is reached during text input.
5277 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5279 Toolkit::TextInput handle( GetOwner() );
5280 mMaxInputCharactersReachedSignalV2.Emit( handle );
5283 void TextInput::EmitInputTextExceedsBoundariesSignal()
5285 // Emit a signal when the input text exceeds the boundaries of the text input.
5287 Toolkit::TextInput handle( GetOwner() );
5288 mInputTextExceedBoundariesSignalV2.Emit( handle );
5291 } // namespace Internal
5293 } // namespace Toolkit