2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
25 #include <dali/public-api/adaptor-framework/virtual-keyboard.h>
26 #include <dali/public-api/animation/constraints.h>
27 #include <dali/public-api/common/stage.h>
28 #include <dali/public-api/events/key-event.h>
29 #include <dali/public-api/events/touch-event.h>
30 #include <dali/public-api/object/type-registry.h>
31 #include <dali/public-api/object/property-notification.h>
32 #include <dali/integration-api/debug.h>
33 #include <dali/public-api/images/resource-image.h>
36 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
37 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
38 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
39 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
47 #if defined(DEBUG_ENABLED)
48 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
51 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
52 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
53 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
54 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
55 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
56 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
58 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
59 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
60 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
61 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
62 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
64 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
65 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
66 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
67 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
68 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
70 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
71 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
72 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
73 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
74 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
75 const float CURSOR_THICKNESS(4.0f);
76 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
77 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
79 const std::string NEWLINE( "\n" );
81 const TextStyle DEFAULT_TEXT_STYLE;
83 const unsigned int SCROLL_TICK_INTERVAL = 50u;
84 const float SCROLL_THRESHOLD = 10.f;
85 const float SCROLL_SPEED = 15.f;
88 * Selection state enumeration (FSM)
92 SelectionNone, ///< Currently not encountered selected section.
93 SelectionStarted, ///< Encountered selected section
94 SelectionFinished ///< Finished selected section
97 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
99 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
103 if( ( *it ).mIsVisible )
105 return --cursorPosition;
114 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
116 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
118 if( ( *it ).mIsVisible )
120 return cursorPosition;
126 return cursorPosition;
130 * Whether the given position plus the cursor size offset is inside the given boundary.
132 * @param[in] position The given position.
133 * @param[in] cursorSize The cursor size.
134 * @param[in] controlSize The given boundary.
136 * @return whether the given position is inside the given boundary.
138 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
140 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
141 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
142 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
143 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
147 * Splits a text in two halves.
149 * If the text's number of characters is odd, firstHalf has one more character.
151 * @param[in] text The text to be split.
152 * @param[out] firstHalf The first half of the text.
153 * @param[out] secondHalf The second half of the text.
155 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
156 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
157 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
162 const std::size_t textLength = text.size();
163 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
165 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
166 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
169 } // end of namespace
177 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
178 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
179 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
180 const Property::Index TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
181 const Property::Index TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
182 const Property::Index TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
183 const Property::Index TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
184 const Property::Index TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
185 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
186 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
187 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+10;
188 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+11;
189 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+12;
190 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+13;
191 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+14;
192 const Property::Index TextInput::CURSOR_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+15;
203 const char* const SIGNAL_START_INPUT = "start-input";
204 const char* const SIGNAL_END_INPUT = "end-input";
205 const char* const SIGNAL_STYLE_CHANGED = "style-changed";
206 const char* const SIGNAL_MAX_INPUT_CHARACTERS_REACHED = "max-input-characters-reached";
207 const char* const SIGNAL_TOOLBAR_DISPLAYED = "toolbar-displayed";
208 const char* const SIGNAL_TEXT_EXCEED_BOUNDARIES = "text-exceed-boundaries";
212 return Toolkit::TextInput::New();
215 TypeRegistration typeRegistration( typeid( Toolkit::TextInput ), typeid( Toolkit::Control ), Create );
217 SignalConnectorType signalConnector1( typeRegistration, SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
218 SignalConnectorType signalConnector2( typeRegistration, SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
219 SignalConnectorType signalConnector3( typeRegistration, SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
220 SignalConnectorType signalConnector4( typeRegistration, SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
221 SignalConnectorType signalConnector5( typeRegistration, SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
222 SignalConnectorType signalConnector6( typeRegistration, SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
226 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
227 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
228 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
229 PropertyRegistration property4( typeRegistration, "cut-and-paste-icon-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
230 PropertyRegistration property5( typeRegistration, "cut-and-paste-icon-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
231 PropertyRegistration property6( typeRegistration, "cut-and-paste-text-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
232 PropertyRegistration property7( typeRegistration, "cut-and-paste-text-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
233 PropertyRegistration property8( typeRegistration, "cut-and-paste-border-color", Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
234 PropertyRegistration property9( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
235 PropertyRegistration property10( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
236 PropertyRegistration property11( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
237 PropertyRegistration property12( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
238 PropertyRegistration property13( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
239 PropertyRegistration property14( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
240 PropertyRegistration property15( typeRegistration, "popup-offset-from-text", Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
241 PropertyRegistration property16( typeRegistration, "cursor-color", Toolkit::TextInput::CURSOR_COLOR_PROPERTY, Property::VECTOR4, &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);
271 handle.SetName( "TextInput");
273 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 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
316 mOverrideAutomaticAlignment( false ),
317 mCursorRTLEnabled( false ),
318 mClosestCursorPositionEOL ( false ),
319 mCursorBlinkStatus( true ),
320 mCursorVisibility( false ),
321 mGrabHandleVisibility( false ),
322 mIsCursorInScrollArea( true ),
323 mIsGrabHandleInScrollArea( true ),
324 mEditModeActive( false ),
325 mEditOnTouch( true ),
326 mTextSelection( true ),
327 mExceedEnabled( true ),
328 mGrabHandleEnabled( true ),
329 mIsSelectionHandleFlipEnabled( true ),
330 mPreEditFlag( false ),
331 mIgnoreCommitFlag( false ),
332 mIgnoreFirstCommitFlag( false ),
333 mSelectingText( false ),
334 mPreserveCursorPosition( false ),
335 mSelectTextOnCommit( false ),
336 mUnderlinedPriorToPreEdit ( false ),
337 mCommitByKeyInput( false ),
338 mPlaceHolderSet( false ),
339 mMarkUpEnabled( false )
341 // Updates the line height accordingly with the input style.
345 TextInput::~TextInput()
347 StopCursorBlinkTimer();
352 std::string TextInput::GetText() const
356 // Return text-view's text only if the text-input's text is not empty
357 // in order to not to return the placeholder text.
358 if( !mStyledText.empty() )
360 text = mDisplayedTextView.GetText();
366 std::string TextInput::GetMarkupText() const
368 std::string markupString;
369 MarkupProcessor::GetMarkupString( mStyledText, markupString );
374 void TextInput::ShowPlaceholderText( const MarkupProcessor::StyledTextArray& stylePlaceHolderText )
376 mDisplayedTextView.SetText( stylePlaceHolderText );
377 mPlaceHolderSet = true;
378 mDisplayedTextView.SetScrollPosition( Vector2( 0.0f,0.0f ) );
381 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
383 // Get the placeholder styled text array from the markup string.
384 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
385 if( mStyledText.empty() )
387 ShowPlaceholderText( mStyledPlaceHolderText );
391 std::string TextInput::GetPlaceholderText()
393 // Traverses the styled placeholder array getting only the text.
394 // Note that for some languages a 'character' could be represented by more than one 'char'
396 std::string placeholderText;
397 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
399 placeholderText.append( (*it).mText.GetText() );
402 return placeholderText ;
405 void TextInput::SetInitialText(const std::string& initialText)
407 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
409 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
411 mPreEditFlag = false;
412 mIgnoreCommitFlag = true;
415 SetText( initialText );
416 PreEditReset( false ); // Reset keyboard as text changed
419 void TextInput::SetText(const std::string& initialText)
421 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
423 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
425 if( mStyledText.empty() )
427 ShowPlaceholderText( mStyledPlaceHolderText );
431 mDisplayedTextView.SetText( mStyledText );
432 mPlaceHolderSet = false;
437 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
439 ImfManager imfManager = ImfManager::Get();
442 imfManager.SetCursorPosition( mCursorPosition );
443 imfManager.SetSurroundingText( initialText );
444 imfManager.NotifyCursorPosition();
447 if( IsScrollEnabled() )
449 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
452 ShowGrabHandleAndSetVisibility( false );
461 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
463 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
465 mDisplayedTextView.SetText( styleText );
466 mPlaceHolderSet = false;
468 // If text alignment hasn't been manually set by application developer, then we
469 // automatically determine the alignment based on the content of the text i.e. what
470 // language the text begins with.
471 // TODO: This should determine different alignments for each line (broken by '\n') of text.
472 if(!mOverrideAutomaticAlignment)
474 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
475 bool leftToRight(true);
477 if( !styleText.empty() )
479 bool breakOut(false);
481 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
483 const Text& text = textIter->mText;
485 for( std::size_t i = 0; i < text.GetLength(); ++i )
487 Character character( text[i] );
488 if( character.GetCharacterDirection() != Character::Neutral )
490 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
498 // Based on this direction, either left or right align text if not manually set by application developer.
499 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
500 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
501 Toolkit::Alignment::VerticalTop ) );
502 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
508 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
510 mMaxStringLength = maxChars;
513 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
515 DALI_ASSERT_DEBUG( maxLines > 0 )
519 mNumberOflinesLimit = maxLines;
523 std::size_t TextInput::GetNumberOfLinesLimit() const
525 return mNumberOflinesLimit;
528 std::size_t TextInput::GetNumberOfCharacters() const
530 return mStyledText.size();
534 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
536 mMaterialColor = color;
539 const Vector4& TextInput::GetMaterialDiffuseColor() const
541 return mMaterialColor;
546 Toolkit::TextInput::InputSignalType& TextInput::InputStartedSignal()
548 return mInputStartedSignal;
551 Toolkit::TextInput::InputSignalType& TextInput::InputFinishedSignal()
553 return mInputFinishedSignal;
556 Toolkit::TextInput::InputSignalType& TextInput::CutAndPasteToolBarDisplayedSignal()
558 return mCutAndPasteToolBarDisplayed;
561 Toolkit::TextInput::StyleChangedSignalType& TextInput::StyleChangedSignal()
563 return mStyleChangedSignal;
566 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
568 return mTextModifiedSignal;
571 Toolkit::TextInput::MaxInputCharactersReachedSignalType& TextInput::MaxInputCharactersReachedSignal()
573 return mMaxInputCharactersReachedSignal;
576 Toolkit::TextInput::InputTextExceedBoundariesSignalType& TextInput::InputTextExceedBoundariesSignal()
578 return mInputTextExceedBoundariesSignal;
581 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
583 Dali::BaseHandle handle( object );
585 bool connected( true );
586 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( handle );
588 if( 0 == strcmp( signalName.c_str(), SIGNAL_START_INPUT ) )
590 textInput.InputStartedSignal().Connect( tracker, functor );
592 else if( 0 == strcmp( signalName.c_str(), SIGNAL_END_INPUT ) )
594 textInput.InputFinishedSignal().Connect( tracker, functor );
596 else if( 0 == strcmp( signalName.c_str(), SIGNAL_STYLE_CHANGED ) )
598 textInput.StyleChangedSignal().Connect( tracker, functor );
600 else if( 0 == strcmp( signalName.c_str(), SIGNAL_MAX_INPUT_CHARACTERS_REACHED ) )
602 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
604 else if( 0 == strcmp( signalName.c_str(), SIGNAL_TOOLBAR_DISPLAYED ) )
606 textInput.CutAndPasteToolBarDisplayedSignal().Connect( tracker, functor );
608 else if( 0 == strcmp( signalName.c_str(), SIGNAL_TEXT_EXCEED_BOUNDARIES ) )
610 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
614 // signalName does not match any signal
621 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
625 // update line height before calculate the actual position.
630 if( setCursorOnTouchPoint )
632 // Sets the cursor position for the given touch point.
633 ReturnClosestIndex( touchPoint, mCursorPosition );
635 // Creates the grab handle.
636 if( IsGrabHandleEnabled() )
638 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
642 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
643 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
644 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
645 ShowGrabHandleAndSetVisibility( true );
647 // Scrolls the text-view if needed.
648 if( IsScrollEnabled() )
650 ScrollTextViewToMakeCursorVisible( cursorPosition );
656 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
668 bool TextInput::IsEditable() const
670 return mEditModeActive;
673 void TextInput::SetEditOnTouch( bool editOnTouch )
675 mEditOnTouch = editOnTouch;
678 bool TextInput::IsEditOnTouch() const
683 void TextInput::SetTextSelectable( bool textSelectable )
685 mTextSelection = textSelectable;
688 bool TextInput::IsTextSelectable() const
690 return mTextSelection;
693 bool TextInput::IsTextSelected() const
698 void TextInput::DeSelectText()
705 void TextInput::SetGrabHandleImage(Dali::Image image )
709 CreateGrabHandle(image);
713 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
715 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
719 mCursor.SetImage( image );
720 mCursor.SetNinePatchBorder( border );
724 Vector3 TextInput::GetSelectionHandleSize()
726 return DEFAULT_SELECTION_HANDLE_SIZE;
729 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
731 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
735 mCursorRTL.SetImage( image);
736 mCursorRTL.SetNinePatchBorder( border );
740 void TextInput::EnableGrabHandle(bool toggle)
742 // enables grab handle with will in turn de-activate magnifier
743 mGrabHandleEnabled = toggle;
746 bool TextInput::IsGrabHandleEnabled()
748 // if false then magnifier will be shown instead.
749 return mGrabHandleEnabled;
752 void TextInput::EnableSelectionHandleFlip( bool toggle )
754 // Deprecated function. To be removed.
755 mIsSelectionHandleFlipEnabled = toggle;
758 bool TextInput::IsSelectionHandleFlipEnabled()
760 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
764 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
766 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
767 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
768 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
770 mSelectionHandleFlipMargin = margin;
773 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
775 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
776 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
778 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
779 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
781 const Vector4 boundary( originX,
783 originX + boundingRectangle.width,
784 originY + boundingRectangle.height );
786 mBoundingRectangleWorldCoordinates = boundary;
789 const Rect<float> TextInput::GetBoundingRectangle() const
791 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
793 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
794 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
796 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
801 const Vector4& TextInput::GetSelectionHandleFlipMargin()
803 return mSelectionHandleFlipMargin;
806 void TextInput::SetTextColor( const Vector4& color )
808 mDisplayedTextView.SetColor( color );
811 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
813 if( style != mInputStyle )
816 bool emitSignal = false;
818 // mask: modify style according to mask, if different emit signal.
819 const TextStyle oldInputStyle( mInputStyle );
821 // Copy the new style.
822 mInputStyle.Copy( style, mask );
824 // if style has changed, emit signal.
825 if( oldInputStyle != mInputStyle )
830 // Updates the line height accordingly with the input style.
833 // Changing font point size will require the cursor to be re-sized
838 EmitStyleChangedSignal();
843 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
845 if ( IsTextSelected() )
847 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
848 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
850 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
852 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
855 // Keeps the old style to be compared with the new one.
856 const TextStyle oldInputStyle( mInputStyle );
858 // Copy only those parameters from the style which are set in the mask.
859 mInputStyle.Copy( style, mask );
861 if( mInputStyle != oldInputStyle )
863 // Updates the line height accordingly with the input style.
866 EmitStyleChangedSignal();
871 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
873 if( !mStyledText.empty() )
875 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
879 TextStyle TextInput::GetStyleAtCursor() const
883 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
885 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
886 style = mStyledText.at( mCursorPosition-1 ).mStyle;
892 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
894 Dali::Font defaultFont = Dali::Font::New();
895 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
902 TextStyle TextInput::GetStyleAt( std::size_t position ) const
904 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
906 if( position >= mStyledText.size() )
908 position = mStyledText.size() - 1;
911 return mStyledText.at( position ).mStyle;
914 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
916 mDisplayedTextView.SetTextAlignment( align );
917 mOverrideAutomaticAlignment = true;
920 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
922 mDisplayedTextView.SetLineJustification( justification );
923 mOverrideAutomaticAlignment = true;
926 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
928 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
931 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
933 return mDisplayedTextView.GetFadeBoundary();
936 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
938 return mDisplayedTextView.GetTextAlignment();
941 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
943 mDisplayedTextView.SetMultilinePolicy( policy );
946 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
948 return mDisplayedTextView.GetMultilinePolicy();
951 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
953 mDisplayedTextView.SetWidthExceedPolicy( policy );
956 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
958 return mDisplayedTextView.GetWidthExceedPolicy();
961 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
963 mDisplayedTextView.SetHeightExceedPolicy( policy );
966 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
968 return mDisplayedTextView.GetHeightExceedPolicy();
971 void TextInput::SetExceedEnabled( bool enable )
973 mExceedEnabled = enable;
976 bool TextInput::GetExceedEnabled() const
978 return mExceedEnabled;
981 void TextInput::SetBackground(Dali::Image image )
983 // TODO Should add this function and add public api to match.
986 bool TextInput::OnTouchEvent(const TouchEvent& event)
991 bool TextInput::OnKeyEvent(const KeyEvent& event)
993 switch( event.state )
997 return OnKeyDownEvent(event);
1003 return OnKeyUpEvent(event);
1015 void TextInput::OnKeyInputFocusGained()
1017 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1019 mEditModeActive = true;
1021 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1023 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1025 // Updates the line height accordingly with the input style.
1028 // Connect the signals to use in text input.
1029 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1030 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1032 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1035 GetTextLayoutInfo();
1038 SetCursorVisibility( true );
1039 StartCursorBlinkTimer();
1041 Toolkit::TextInput handle( GetOwner() );
1042 mInputStartedSignal.Emit( handle );
1044 ImfManager imfManager = ImfManager::Get();
1048 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1050 // Notify that the text editing start.
1051 imfManager.Activate();
1053 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1054 imfManager.SetRestoreAfterFocusLost( true );
1056 imfManager.SetCursorPosition( mCursorPosition );
1057 imfManager.NotifyCursorPosition();
1060 mClipboard = Clipboard::Get(); // Store handle to clipboard
1062 // Now in edit mode we can accept string to paste from clipboard
1063 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1066 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1070 void TextInput::OnKeyInputFocusLost()
1072 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1076 // If key input focus is lost, it removes the
1077 // underline from the last pre-edit text.
1078 RemovePreEditStyle();
1079 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1080 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1084 ImfManager imfManager = ImfManager::Get();
1087 // The text editing is finished. Therefore the imf manager don't have restore activation.
1088 imfManager.SetRestoreAfterFocusLost( false );
1090 // Notify that the text editing finish.
1091 imfManager.Deactivate();
1093 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1095 // Disconnect signal used the text input.
1096 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1098 Toolkit::TextInput handle( GetOwner() );
1099 mInputFinishedSignal.Emit( handle );
1100 mEditModeActive = false;
1101 mPreEditFlag = false;
1103 SetCursorVisibility( false );
1104 StopCursorBlinkTimer();
1106 ShowGrabHandleAndSetVisibility( false );
1109 // No longer in edit mode so do not want to receive string from clipboard
1110 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1113 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1116 Clipboard clipboard = Clipboard::Get();
1119 clipboard.HideClipboard();
1123 void TextInput::OnControlStageConnection()
1125 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1127 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1129 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1133 void TextInput::CreateActiveLayer()
1135 Actor self = Self();
1136 mActiveLayer = Layer::New();
1137 mActiveLayer.SetName ( "ActiveLayerActor" );
1139 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1140 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1141 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1143 self.Add( mActiveLayer );
1144 mActiveLayer.RaiseToTop();
1147 void TextInput::OnInitialize()
1149 CreateTextViewActor();
1153 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1154 // different positions depending on language)
1155 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1156 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1158 Actor self = Self();
1159 self.Add( mCursor );
1160 self.Add( mCursorRTL );
1162 mCursorVisibility = false;
1164 CreateActiveLayer(); // todo move this so layer only created when needed.
1166 // Assign names to image actors
1167 mCursor.SetName("mainCursor");
1168 mCursorRTL.SetName("rtlCursor");
1171 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1173 mDisplayedTextView.SetSize( targetSize );
1174 GetTextLayoutInfo();
1175 mActiveLayer.SetSize(targetSize);
1178 void TextInput::OnRelayout( const Vector2& size, ActorSizeContainer& container )
1180 Relayout( mDisplayedTextView, size, container );
1181 Relayout( mPopupPanel.GetRootActor(), size, container );
1183 GetTextLayoutInfo();
1188 Vector3 TextInput::GetNaturalSize()
1190 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1192 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1194 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1195 naturalSize.height = mLineHeight;
1201 float TextInput::GetHeightForWidth( float width )
1203 float height = mDisplayedTextView.GetHeightForWidth( width );
1205 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1207 // If the height is zero, it means there is no text. Let's return the cursor height.
1208 height = mLineHeight;
1214 /*end of Virtual methods from parent*/
1216 // Private Internal methods
1218 void TextInput::OnHandlePan(Actor actor, const PanGesture& gesture)
1220 switch (gesture.state)
1222 case Gesture::Started:
1223 // fall through so code not duplicated
1224 case Gesture::Continuing:
1226 if (actor == mGrabArea)
1228 SetCursorVisibility( true );
1229 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1230 MoveGrabHandle( gesture.displacement );
1231 HidePopup(); // Do not show popup whilst handle is moving
1233 else if (actor == mHandleOneGrabArea)
1235 // the displacement in PanGesture is affected by the actor's rotation.
1236 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1237 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1239 MoveSelectionHandle( HandleOne, gesture.displacement );
1241 mState = StateDraggingHandle;
1244 else if (actor == mHandleTwoGrabArea)
1246 // the displacement in PanGesture is affected by the actor's rotation.
1247 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1248 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1250 MoveSelectionHandle( HandleTwo, gesture.displacement );
1252 mState = StateDraggingHandle;
1258 case Gesture::Finished:
1260 // Revert back to non-pressed selection handle images
1261 if (actor == mGrabArea)
1263 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1264 SetCursorVisibility( true );
1265 SetUpPopupSelection();
1268 if (actor == mHandleOneGrabArea)
1270 // the displacement in PanGesture is affected by the actor's rotation.
1271 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1272 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1274 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1276 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1278 ShowPopupCutCopyPaste();
1280 if (actor == mHandleTwoGrabArea)
1282 // the displacement in PanGesture is affected by the actor's rotation.
1283 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1284 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1286 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1288 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1290 ShowPopupCutCopyPaste();
1299 // Stop the flashing animation so easy to see when moved.
1300 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1302 if (touch.GetPoint(0).state == TouchPoint::Down)
1304 SetCursorVisibility( true );
1305 StopCursorBlinkTimer();
1307 else if (touch.GetPoint(0).state == TouchPoint::Up)
1309 SetCursorVisibility( true );
1310 StartCursorBlinkTimer();
1315 // selection handle one
1316 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1318 if (touch.GetPoint(0).state == TouchPoint::Down)
1320 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1322 else if (touch.GetPoint(0).state == TouchPoint::Up)
1324 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1329 // selection handle two
1330 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1332 if (touch.GetPoint(0).state == TouchPoint::Down)
1334 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1336 else if (touch.GetPoint(0).state == TouchPoint::Up)
1338 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1343 void TextInput::OnDoubleTap(Dali::Actor actor, const Dali::TapGesture& tap)
1345 // If text exists then select nearest word.
1346 if ( !mStyledText.empty())
1350 ShowGrabHandleAndSetVisibility( false );
1355 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1356 // converts the pre-edit word being displayed to a committed word.
1357 if ( !mUnderlinedPriorToPreEdit )
1360 style.SetUnderline( false );
1361 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1363 mPreEditFlag = false;
1364 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1365 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1366 PreEditReset( false );
1368 mCursorPosition = 0;
1370 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1371 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1373 std::size_t start = 0;
1374 std::size_t end = 0;
1375 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1377 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1379 ImfManager imfManager = ImfManager::Get();
1382 imfManager.SetCursorPosition ( mCursorPosition );
1383 imfManager.NotifyCursorPosition();
1386 if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1388 SelectText( start, end );
1389 ShowPopupCutCopyPaste();
1393 RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1394 HidePopup( false ); // Hide popup with setting to do auto show.
1395 SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1399 else if ( mClipboard && mClipboard.NumberOfItems() )
1401 ShowPopupCutCopyPaste();
1404 // If no text and clipboard empty then do nothing
1407 // TODO: Change the function name to be more general.
1408 void TextInput::OnTextTap(Dali::Actor actor, const Dali::TapGesture& tap)
1410 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1411 , (mEditOnTouch)?"true":"false"
1412 , (mEditModeActive)?"true":"false");
1414 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1419 if( mGrabArea == actor )
1421 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1423 SetUpPopupSelection();
1433 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1435 // Initially don't create the grab handle.
1436 bool createGrabHandle = false;
1438 if ( !mEditModeActive )
1440 // update line height before calculate the actual position.
1443 // Only start edit mode if TextInput configured to edit on touch
1446 // Set the initial cursor position in the tap point.
1447 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1453 // Show the keyboard if it was hidden.
1454 if (!VirtualKeyboard::IsVisible())
1456 VirtualKeyboard::Show();
1459 // Reset keyboard as tap event has occurred.
1460 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1461 PreEditReset( true );
1463 GetTextLayoutInfo();
1465 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1467 // 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.
1469 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1471 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1473 // Notify keyboard so it can 're-capture' word for predictive text.
1474 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1475 ImfManager imfManager = ImfManager::Get();
1478 imfManager.SetCursorPosition ( mCursorPosition );
1479 imfManager.NotifyCursorPosition();
1481 const TextStyle oldInputStyle( mInputStyle );
1483 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1487 // Create the grab handle.
1488 // Grab handle is created later.
1489 createGrabHandle = true;
1491 if( oldInputStyle != mInputStyle )
1493 // Updates the line height accordingly with the input style.
1496 EmitStyleChangedSignal();
1501 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1502 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1503 // otherwise the Grab handle will be shown when selecting.
1504 if ( createGrabHandle && IsGrabHandleEnabled() )
1506 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1507 bool altPositionValid; // Alternate cursor validity flag.
1508 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1509 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1511 if( altPositionValid )
1513 // Check which of the positions is the closest.
1514 if( fabsf( altPosition.x - tap.localPoint.x ) < fabsf( cursorPosition.x - tap.localPoint.x ) )
1516 cursorPosition = altPosition;
1522 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1523 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1524 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1525 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1530 void TextInput::OnLongPress(Dali::Actor actor, const Dali::LongPressGesture& longPress)
1532 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1534 // Ignore longpress if in selection mode already
1536 if(longPress.state == Dali::Gesture::Started)
1538 // Start edit mode on long press
1539 if ( !mEditModeActive )
1544 // If text exists then select nearest word.
1545 if ( !mStyledText.empty())
1549 ShowGrabHandleAndSetVisibility( false );
1554 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1555 // converts the pre-edit word being displayed to a committed word.
1556 if ( !mUnderlinedPriorToPreEdit )
1559 style.SetUnderline( false );
1560 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1562 mPreEditFlag = false;
1563 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1564 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1565 PreEditReset( false );
1567 mCursorPosition = 0;
1569 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1570 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1572 std::size_t start = 0;
1573 std::size_t end = 0;
1574 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1576 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1578 ImfManager imfManager = ImfManager::Get();
1581 imfManager.SetCursorPosition ( mCursorPosition );
1582 imfManager.NotifyCursorPosition();
1585 SelectText( start, end );
1588 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1589 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1591 ShowPopupCutCopyPaste();
1596 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1598 const Text clipboardText( notifier.GetContent() );
1599 PasteText( clipboardText );
1601 SetCursorVisibility( true );
1602 StartCursorBlinkTimer();
1604 ShowGrabHandleAndSetVisibility( false );
1610 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1612 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1614 const std::string& name = button.GetName();
1616 if(name == TextInputPopup::OPTION_SELECT_WORD)
1618 std::size_t start = 0;
1619 std::size_t end = 0;
1620 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1622 SelectText( start, end );
1624 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1626 SetCursorVisibility(false);
1627 StopCursorBlinkTimer();
1629 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1630 std::size_t start = 0;
1632 SelectText( start, end );
1634 else if(name == TextInputPopup::OPTION_CUT)
1636 bool ret = CopySelectedTextToClipboard();
1640 DeleteHighlightedText( true );
1644 SetCursorVisibility( true );
1645 StartCursorBlinkTimer();
1649 else if(name == TextInputPopup::OPTION_COPY)
1651 CopySelectedTextToClipboard();
1655 SetCursorVisibility( true );
1656 StartCursorBlinkTimer();
1660 else if(name == TextInputPopup::OPTION_PASTE)
1662 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1664 PasteText(retrievedString);
1666 SetCursorVisibility( true );
1667 StartCursorBlinkTimer();
1669 ShowGrabHandleAndSetVisibility( false );
1673 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1675 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1676 // Hence pass the false parameter for signalFinished.
1677 HidePopup( true, false );
1678 mClipboard.ShowClipboard();
1684 bool TextInput::OnCursorBlinkTimerTick()
1687 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1688 if ( mCursorRTLEnabled )
1690 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1692 mCursorBlinkStatus = !mCursorBlinkStatus;
1697 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1699 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1701 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1704 //FIXME this routine needs to be re-written as it contains too many branches.
1705 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1707 std::string keyName = event.keyPressedName;
1708 std::string keyString = event.keyPressed;
1710 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1712 // Do not consume "Tab" and "Escape" keys.
1713 if(keyName == "Tab" || keyName == "Escape")
1715 // Escape key to end the edit mode
1721 HidePopup(); // If Pop-up shown then hides it as editing text.
1723 // Update Flag, indicates whether to update the text-input contents or not.
1724 // Any key stroke that results in a visual change of the text-input should
1725 // set this flag to true.
1728 // Whether to scroll text to cursor position.
1729 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1730 bool scroll = false;
1732 if (keyName == "Return")
1734 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1736 bool preEditFlagPreviouslySet( mPreEditFlag );
1738 // replaces highlighted text with new line
1739 DeleteHighlightedText( false );
1741 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1743 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1744 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1747 mCommitByKeyInput = true;
1750 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1751 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1753 mPreEditFlag = true;
1754 mIgnoreCommitFlag = false;
1764 else if ( keyName == "space" )
1767 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1769 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1770 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1773 mCommitByKeyInput = true;
1778 else if (keyName == "BackSpace")
1781 if ( mCursorPosition > 0 )
1783 DeleteCharacter( mCursorPosition );
1789 else if (keyName == "Right")
1794 else if (keyName == "Left")
1796 AdvanceCursor(true);
1799 else // event is a character
1801 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1802 if ( !keyString.empty() )
1804 // replaces highlighted text with new character
1805 DeleteHighlightedText( false );
1807 // Received key String
1808 mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1814 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1815 // as this is a costly operation.
1821 if(update || scroll)
1823 if( IsScrollEnabled() )
1825 // Calculates the new cursor position (in actor coordinates)
1826 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1828 ScrollTextViewToMakeCursorVisible( cursorPosition );
1835 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1837 std::string keyName = event.keyPressedName;
1838 std::string keyString = event.keyPressed;
1840 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1842 // The selected text become deselected when the key code is DALI_KEY_BACK.
1843 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1852 void TextInput::ChooseRtlSelectionHandlePosition( const Vector3& cursorPositionOne,
1853 const Vector3& cursorPositionTwo,
1854 bool altPositionValidOne,
1855 bool altPositionValidTwo,
1856 const Vector3& altPositionOne,
1857 const Vector3& altPositionTwo )
1859 // TODO VCC Valid for one line.
1860 // Try to place the selection handles. TODO think in something better. Probably need to know the direction of the paragraph.
1861 if( cursorPositionOne != cursorPositionTwo )
1863 if( cursorPositionOne.x < cursorPositionTwo.x )
1865 mSelectionHandleOneActualPosition = cursorPositionOne;
1866 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1870 mSelectionHandleOneActualPosition = cursorPositionTwo;
1871 mSelectionHandleTwoActualPosition = cursorPositionOne;
1876 mSelectionHandleOneActualPosition = cursorPositionOne;
1877 if( altPositionValidOne )
1879 if( altPositionOne.x < mSelectionHandleOneActualPosition.x )
1881 mSelectionHandleOneActualPosition = altPositionOne;
1884 if( altPositionValidTwo )
1886 if( altPositionTwo.x < mSelectionHandleOneActualPosition.x )
1888 mSelectionHandleOneActualPosition = altPositionTwo;
1892 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1893 if( altPositionValidTwo )
1895 if( altPositionTwo.x > mSelectionHandleTwoActualPosition.x )
1897 mSelectionHandleTwoActualPosition = altPositionTwo;
1900 if( altPositionValidOne )
1902 if( altPositionOne.x > mSelectionHandleTwoActualPosition.x )
1904 mSelectionHandleTwoActualPosition = altPositionOne;
1910 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1912 // Updates the stored scroll position.
1913 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1915 const Vector3& controlSize = GetControlSize();
1916 Size cursorSize( CURSOR_THICKNESS, 0.f );
1918 // Updates the cursor and grab handle position and visibility.
1919 if( mGrabHandle || mCursor )
1921 cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1923 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1924 bool altPositionValid; // Alternate cursor validity flag.
1925 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1926 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1928 if( altPositionValid )
1930 // Check which of the positions is the closest.
1931 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( cursorPosition.x - mActualGrabHandlePosition.x ) )
1933 cursorPosition = altPosition;
1937 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1939 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1943 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1944 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1949 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1950 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1954 // Updates the selection handles and highlighted text position and visibility.
1955 if( mSelectionHandleOne && mSelectionHandleTwo )
1957 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
1958 bool altPositionValidOne; // Alternate cursor validity flag.
1959 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1960 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
1962 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
1963 bool altPositionValidTwo; // Alternate cursor validity flag.
1964 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1965 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
1967 // VCC TODO: This method is a hack for one line.
1968 ChooseRtlSelectionHandlePosition( cursorPositionOne,
1970 altPositionValidOne,
1971 altPositionValidTwo,
1975 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1976 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1977 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1978 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1980 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1981 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1982 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1983 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1988 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1990 // Scroll the text to make the cursor visible.
1991 const Size cursorSize( CURSOR_THICKNESS,
1992 GetRowRectFromCharacterPosition( mCursorPosition ).height );
1994 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1996 const Vector3& controlSize = GetControlSize();
1998 // Calculates the new scroll position.
1999 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
2000 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
2002 scrollOffset.x += cursorPosition.x;
2005 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
2007 scrollOffset.y += cursorPosition.y;
2010 // Sets the new scroll position.
2011 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
2012 SetScrollPosition( scrollOffset );
2015 void TextInput::StartScrollTimer()
2019 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
2020 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
2023 if( !mScrollTimer.IsRunning() )
2025 mScrollTimer.Start();
2029 void TextInput::StopScrollTimer()
2033 mScrollTimer.Stop();
2037 bool TextInput::OnScrollTimerTick()
2039 // TODO: need to set the new style accordingly the new handle position.
2041 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
2043 // nothing to do if all handles are invisible or doesn't exist.
2049 // Choose between the grab handle or the selection handles.
2050 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
2051 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
2052 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
2054 std::size_t newCursorPosition = 0;
2055 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2057 // Whether the handle's position is different of the previous one and in the case of the selection handle,
2058 // the new selection handle's position needs to be different of the other one.
2059 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2060 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2061 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2063 if( differentSelectionHandles )
2065 handlePosition = newCursorPosition;
2067 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2069 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2071 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2072 scrollPosition += scrollDelta;
2073 SetScrollPosition( scrollPosition );
2075 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2080 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2083 actualHandlePosition.x += mScrollDisplacement.x;
2084 actualHandlePosition.y += mScrollDisplacement.y;
2089 // Public Internal Methods (public for testing purpose)
2091 void TextInput::SetUpTouchEvents()
2093 if ( !mTapDetector )
2095 mTapDetector = TapGestureDetector::New();
2096 // Attach the actors and connect the signal
2097 mTapDetector.Attach(Self());
2099 // As contains children which may register for tap the default control detector is not used.
2100 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2103 if ( !mDoubleTapDetector )
2105 mDoubleTapDetector = TapGestureDetector::New( 2 );
2106 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2108 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2109 // so that we do not, unnecessarily, have a double tap request all the time
2112 if ( !mPanGestureDetector )
2114 mPanGestureDetector = PanGestureDetector::New();
2115 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2118 if ( !mLongPressDetector )
2120 mLongPressDetector = LongPressGestureDetector::New();
2121 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2122 mLongPressDetector.Attach(Self());
2126 void TextInput::CreateTextViewActor()
2128 mDisplayedTextView = Toolkit::TextView::New();
2129 mDisplayedTextView.SetName( "DisplayedTextView ");
2130 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2131 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2132 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2133 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2134 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2135 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2136 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2137 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2138 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2139 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2141 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2143 Self().Add( mDisplayedTextView );
2146 // Start a timer to initiate, used by the cursor to blink.
2147 void TextInput::StartCursorBlinkTimer()
2149 if ( !mCursorBlinkTimer )
2151 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2152 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2155 if ( !mCursorBlinkTimer.IsRunning() )
2157 mCursorBlinkTimer.Start();
2161 // Start a timer to initiate, used by the cursor to blink.
2162 void TextInput::StopCursorBlinkTimer()
2164 if ( mCursorBlinkTimer )
2166 mCursorBlinkTimer.Stop();
2170 void TextInput::StartEditMode()
2172 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2174 if(!mEditModeActive)
2179 if ( mDoubleTapDetector )
2181 mDoubleTapDetector.Attach( Self() );
2185 void TextInput::EndEditMode()
2187 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2189 ClearKeyInputFocus();
2191 if ( mDoubleTapDetector )
2193 mDoubleTapDetector.Detach( Self() );
2197 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2199 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2201 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2203 style.SetUnderline( true );
2204 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2208 void TextInput::RemovePreEditStyle()
2210 if ( !mUnderlinedPriorToPreEdit )
2213 style.SetUnderline( false );
2214 SetActiveStyle( style, TextStyle::UNDERLINE );
2218 // IMF related methods
2221 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2223 bool update( false );
2224 bool preeditResetRequired ( false );
2226 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2228 HidePopup(); // If Pop-up shown then hides it as editing text.
2231 switch ( imfEvent.eventName )
2233 case ImfManager::PREEDIT:
2235 mIgnoreFirstCommitFlag = false;
2237 // 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
2239 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2241 if( IsScrollEnabled() )
2243 // Calculates the new cursor position (in actor coordinates)
2244 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2245 ScrollTextViewToMakeCursorVisible( cursorPosition );
2252 case ImfManager::COMMIT:
2254 if( mIgnoreFirstCommitFlag )
2256 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2257 mIgnoreFirstCommitFlag = false;
2261 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2263 // 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
2265 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2266 // not needed, one such scenario is when the pre-edit word is too long to fit.
2267 if ( !mIgnoreCommitFlag )
2269 update = CommitReceived( imfEvent.predictiveString );
2273 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2279 if( IsScrollEnabled() )
2281 // Calculates the new cursor position (in actor coordinates)
2282 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2284 ScrollTextViewToMakeCursorVisible( cursorPosition );
2289 case ImfManager::DELETESURROUNDING:
2291 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2292 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2294 mPreEditFlag = false;
2296 std::size_t toDelete = 0;
2297 std::size_t numberOfCharacters = 0;
2300 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2302 toDelete = mCursorPosition + imfEvent.cursorOffset;
2304 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2306 numberOfCharacters = mStyledText.size() - toDelete;
2310 numberOfCharacters = imfEvent.numberOfChars;
2313 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2314 DeleteRange( toDelete, numberOfCharacters );
2316 mCursorPosition = toDelete;
2317 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2321 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2324 case ImfManager::GETSURROUNDING:
2326 // 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
2327 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2328 if (! ( false || mSelectingText ) )
2330 std::string text( GetText() );
2331 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2333 imfManager.SetCursorPosition( mCursorPosition );
2334 imfManager.SetSurroundingText( text );
2337 if( 0 != mNumberOfSurroundingCharactersDeleted )
2339 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2340 mNumberOfSurroundingCharactersDeleted = 0;
2342 if( mStyledText.empty() )
2344 ShowPlaceholderText( mStyledPlaceHolderText );
2349 case ImfManager::VOID:
2351 DALI_ASSERT_DEBUG( false );
2355 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2357 return callbackData;
2360 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2362 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2364 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2365 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2367 bool preeditResetRequest ( false );
2369 if( mPreEditFlag ) // Already in pre-edit state.
2371 if( mStyledText.size() >= mMaxStringLength )
2373 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2374 // Cannot fit these characters into field, clear pre-edit.
2375 if ( !mUnderlinedPriorToPreEdit )
2378 style.SetUnderline( false );
2379 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2381 mIgnoreCommitFlag = true;
2382 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2383 mPreEditFlag = false;
2384 EmitMaxInputCharactersReachedSignal();
2388 // delete existing pre-edit string
2389 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2391 // Store new pre-edit string
2392 mPreEditString.SetText( keyString );
2394 if ( keyString.empty() )
2396 mPreEditFlag = false;
2397 mCursorPosition = mPreEditStartPosition;
2399 if( mStyledText.empty() )
2401 ShowPlaceholderText( mStyledPlaceHolderText );
2405 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2408 GetTextLayoutInfo();
2413 // Insert new pre-edit string. InsertAt updates the size and position table.
2414 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2415 // 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.
2416 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2417 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2418 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2421 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2425 else // mPreEditFlag not set
2427 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2429 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2430 // new pre-edit so move into pre-edit state by setting flag
2431 mPreEditFlag = true;
2432 mPreEditString.SetText( keyString ); // store new pre-edit string
2433 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2434 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2435 // 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.
2436 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2437 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2438 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2439 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2445 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2449 return preeditResetRequest;
2452 bool TextInput::CommitReceived(const std::string& keyString )
2454 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2455 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2457 bool update( false );
2459 RemovePreEditStyle();
2461 const std::size_t styledTextSize( mStyledText.size() );
2462 if( styledTextSize >= mMaxStringLength )
2464 // Cannot fit these characters into field, clear pre-edit.
2467 mIgnoreCommitFlag = true;
2468 mPreEditFlag = false;
2470 EmitMaxInputCharactersReachedSignal();
2476 // delete existing pre-edit string
2477 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2478 mPreEditFlag = false;
2480 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2481 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2483 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2485 // No need to update cursor position as Cursor location given by touch.
2486 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2487 mPreserveCursorPosition = false;
2491 // Cursor not set by touch so needs to be re-positioned to input more text
2492 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2494 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2495 if ( mCommitByKeyInput )
2497 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2498 mCommitByKeyInput = false;
2504 if ( mSelectTextOnCommit )
2506 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2511 else // mPreEditFlag not set
2513 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2515 if( mStyledText.empty() && mPlaceHolderSet )
2517 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2518 mDisplayedTextView.SetText( "" );
2519 mNumberOfSurroundingCharactersDeleted = 0;
2520 mPlaceHolderSet = false;
2522 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2524 mNumberOfSurroundingCharactersDeleted = 0;
2529 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2534 mSelectTextOnCommit = false;
2536 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2537 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2542 // End of IMF related methods
2544 std::size_t TextInput::DeletePreEdit()
2546 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2548 DALI_ASSERT_DEBUG( mPreEditFlag );
2550 const std::size_t preEditStringLength = mPreEditString.GetLength();
2551 const std::size_t styledTextSize = mStyledText.size();
2553 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2555 // Prevents erase items outside mStyledText bounds.
2556 if( mPreEditStartPosition > styledTextSize )
2558 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2559 mPreEditStartPosition = styledTextSize;
2562 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2564 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2565 endPosition = styledTextSize;
2568 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2570 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2571 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2573 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2575 return preEditStringLength;
2578 void TextInput::PreEditReset( bool preserveCursorPosition )
2580 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2581 preserveCursorPosition, mCursorPosition);
2583 // 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.
2584 mPreserveCursorPosition = preserveCursorPosition;
2586 // Reset incase we are in a pre-edit state.
2587 ImfManager imfManager = ImfManager::Get();
2590 imfManager.Reset(); // Will trigger a commit message
2594 void TextInput::CursorUpdate()
2598 ImfManager imfManager = ImfManager::Get();
2601 std::string text( GetText() );
2602 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2603 imfManager.SetCursorPosition ( mCursorPosition );
2604 imfManager.NotifyCursorPosition();
2608 /* Delete highlighted characters redisplay*/
2609 void TextInput::DeleteHighlightedText( bool inheritStyle )
2611 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2615 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2617 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2618 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2620 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2623 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2625 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2626 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2628 mStyledText.erase(itStart, itEnd);
2630 // update the selection handles if they are visible.
2632 // 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.
2635 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2637 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2638 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2639 // Mean we do not re-draw the text more than we have too.
2642 /* Delete character at current cursor position and redisplay*/
2643 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2645 // Ensure positionToDelete is not out of bounds.
2646 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2647 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2648 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2650 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2653 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2655 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2657 // Get the styled text of the character to be deleted as it may be needed if
2658 // the "exceed the text-input's boundaries" option is disabled.
2659 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2661 mStyledText.erase(it); // erase the character left of positionToDelete
2663 if( mStyledText.empty() )
2665 ShowPlaceholderText( mStyledPlaceHolderText );
2669 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2671 const Character characterToDelete = styledCharacterToDelete.mText[0];
2673 // It may happen than after removing a white space or a new line character,
2674 // two words merge, this new word could be big enough to not fit in its
2675 // current line, so moved to the next one, and make some part of the text to
2676 // exceed the text-input's boundary.
2677 if( !mExceedEnabled )
2679 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2681 // Get the new text layout after removing one character.
2682 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2684 // Get text-input's size.
2685 const Vector3& size = GetControlSize();
2687 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2688 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2690 MarkupProcessor::StyledTextArray array;
2691 array.push_back( styledCharacterToDelete );
2692 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2694 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2699 GetTextLayoutInfo();
2701 ShowGrabHandleAndSetVisibility( false );
2703 mCursorPosition = positionToDelete -1;
2705 const TextStyle oldInputStyle( mInputStyle );
2707 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2709 if( oldInputStyle != mInputStyle )
2711 // Updates the line height accordingly with the input style.
2714 EmitStyleChangedSignal();
2719 /*Insert new character into the string and (optionally) redisplay text-input*/
2720 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2722 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2724 // Ensure insertionPosition is not out of bounds.
2725 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2727 bool textExceedsMaximunNumberOfCharacters = false;
2728 bool textExceedsBoundary = false;
2729 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2731 ShowGrabHandleAndSetVisibility( false );
2733 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2737 mIgnoreCommitFlag = true;
2738 mPreEditFlag = false;
2739 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2740 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2743 if( textExceedsMaximunNumberOfCharacters )
2745 EmitMaxInputCharactersReachedSignal();
2748 if( textExceedsBoundary )
2750 EmitInputTextExceedsBoundariesSignal();
2751 PreEditReset( false );
2755 return insertedStringLength;
2758 ImageActor TextInput::CreateCursor( const Vector4& color)
2761 cursor = CreateSolidColorActor(color);
2762 cursor.SetName( "Cursor" );
2764 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2765 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2766 cursor.SetVisible(false);
2771 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2773 // As cursor is not moving due to grab handle, handle should be hidden.
2774 ShowGrabHandleAndSetVisibility( false );
2776 bool cursorPositionChanged = false;
2779 if ( mCursorPosition >= places )
2781 mCursorPosition = mCursorPosition - places;
2782 cursorPositionChanged = true;
2787 if ((mCursorPosition + places) <= mStyledText.size())
2789 mCursorPosition = mCursorPosition + places;
2790 cursorPositionChanged = true;
2794 if( cursorPositionChanged )
2796 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2798 const TextStyle oldInputStyle( mInputStyle );
2799 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2803 if( oldInputStyle != mInputStyle )
2805 // Updates the line height accordingly with the input style.
2808 EmitStyleChangedSignal();
2811 ImfManager imfManager = ImfManager::Get();
2814 imfManager.SetCursorPosition ( mCursorPosition );
2815 imfManager.NotifyCursorPosition();
2820 void TextInput::DrawCursor()
2822 const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2824 // Get height of cursor and set its size
2825 Size size( CURSOR_THICKNESS, 0.0f );
2826 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2828 size.height = rowRect.height;
2832 // Measure Font so know how big text will be if no initial text to measure.
2833 size.height = mLineHeight;
2836 mCursor.SetSize(size);
2838 // If the character is italic then the cursor also tilts.
2839 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2841 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2843 if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2845 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2846 bool altPositionValid; // Alternate cursor validity flag.
2847 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2848 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2850 SetAltCursorEnabled( altPositionValid );
2852 if( !altPositionValid )
2854 mCursor.SetPosition( position + UI_OFFSET );
2858 size.height *= 0.5f;
2859 mCursor.SetSize(size);
2860 mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2862 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2863 size.height = rowRect.height * 0.5f;
2864 mCursorRTL.SetSize(size);
2865 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2868 if( IsScrollEnabled() )
2870 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2871 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2876 void TextInput::SetAltCursorEnabled( bool enabled )
2878 mCursorRTLEnabled = enabled;
2879 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2882 void TextInput::SetCursorVisibility( bool visible )
2884 mCursorVisibility = visible;
2885 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2886 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2889 void TextInput::CreateGrabHandle( Dali::Image image )
2895 mGrabHandleImage = ResourceImage::New(DEFAULT_GRAB_HANDLE);
2899 mGrabHandleImage = image;
2902 mGrabHandle = ImageActor::New(mGrabHandleImage);
2903 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2904 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2906 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2908 ShowGrabHandleAndSetVisibility( false );
2910 CreateGrabArea( mGrabHandle );
2912 mActiveLayer.Add(mGrabHandle);
2916 void TextInput::CreateGrabArea( Actor& parent )
2918 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2919 mGrabArea.SetName( "GrabArea" );
2920 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2921 mGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
2922 mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
2923 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2924 mTapDetector.Attach( mGrabArea );
2925 mPanGestureDetector.Attach( mGrabArea );
2926 mLongPressDetector.Attach( mGrabArea );
2928 parent.Add(mGrabArea);
2931 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2933 Vector3 actualHandlePosition;
2937 mActualGrabHandlePosition.x += displacement.x;
2938 mActualGrabHandlePosition.y += displacement.y;
2940 // Grab handle should jump to the nearest character and take cursor with it
2941 std::size_t newCursorPosition = 0;
2942 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2944 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2945 bool altPositionValid; // Alternate cursor validity flag.
2946 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2947 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
2949 if( altPositionValid )
2951 // Check which of the positions is the closest.
2952 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
2954 actualHandlePosition = altPosition;
2958 bool handleVisible = true;
2960 if( IsScrollEnabled() )
2962 const Vector3 controlSize = GetControlSize();
2963 const Size cursorSize = GetRowRectFromCharacterPosition( newCursorPosition );
2964 // Scrolls the text if the handle is not in a visible position
2965 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2972 mCurrentHandlePosition = actualHandlePosition;
2973 mScrollDisplacement = Vector2::ZERO;
2977 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2979 mScrollDisplacement.x = -SCROLL_SPEED;
2981 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2983 mScrollDisplacement.x = SCROLL_SPEED;
2985 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2987 mScrollDisplacement.y = -SCROLL_SPEED;
2989 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2991 mScrollDisplacement.y = SCROLL_SPEED;
2997 if( handleVisible && // Only redraw cursor and do updates if position changed
2998 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3000 mCursorPosition = newCursorPosition;
3002 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3004 const TextStyle oldInputStyle( mInputStyle );
3006 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3008 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3010 if( oldInputStyle != mInputStyle )
3012 // Updates the line height accordingly with the input style.
3015 EmitStyleChangedSignal();
3020 return actualHandlePosition;
3023 void TextInput::ShowGrabHandle( bool visible )
3025 if ( IsGrabHandleEnabled() )
3029 mGrabHandle.SetVisible( mGrabHandleVisibility );
3031 StartMonitoringStageForTouch();
3035 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3037 mGrabHandleVisibility = visible;
3038 ShowGrabHandle( visible );
3041 // Callbacks connected to be Property notifications for Boundary checking.
3043 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3045 mIsSelectionHandleOneFlipped = true;
3046 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3047 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3050 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3052 mIsSelectionHandleOneFlipped = false;
3053 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3054 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3057 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3059 mIsSelectionHandleTwoFlipped = true;
3060 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3061 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3064 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3066 mIsSelectionHandleTwoFlipped = false;
3067 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3068 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3071 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3072 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3074 mSelectionHandleOne.SetOpacity(0.0f);
3077 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3079 mSelectionHandleOne.SetOpacity(1.0f);
3082 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3084 mSelectionHandleTwo.SetOpacity(0.0f);
3087 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3089 mSelectionHandleTwo.SetOpacity(1.0f);
3092 // End of Callbacks connected to be Property notifications for Boundary checking.
3094 void TextInput::SetUpHandlePropertyNotifications()
3096 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3098 Vector3 handlesize = GetSelectionHandleSize();
3100 // Exceeding horizontal boundary
3101 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WorldPositionX, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3102 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3104 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WorldPositionX, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3105 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3107 // Within horizontal boundary
3108 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WorldPositionX, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3109 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3111 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WorldPositionX, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3112 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3114 // Exceeding vertical boundary
3115 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WorldPositionY,
3116 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3117 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3118 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3120 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WorldPositionY,
3121 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3122 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3123 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3125 // Within vertical boundary
3126 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WorldPositionY,
3127 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3128 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3129 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3131 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WorldPositionY,
3132 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3133 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3134 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3137 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3139 mSelectionHandleOnePosition = start;
3140 mSelectionHandleTwoPosition = end;
3142 if ( !mSelectionHandleOne )
3144 // create normal and pressed images
3145 mSelectionHandleOneImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
3146 mSelectionHandleOneImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3148 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3149 mSelectionHandleOne.SetName("SelectionHandleOne");
3150 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3151 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3152 mIsSelectionHandleOneFlipped = false;
3153 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3155 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3156 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3158 mHandleOneGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
3159 mHandleOneGrabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
3160 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3162 mTapDetector.Attach( mHandleOneGrabArea );
3163 mPanGestureDetector.Attach( mHandleOneGrabArea );
3165 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3167 mSelectionHandleOne.Add( mHandleOneGrabArea );
3168 mActiveLayer.Add( mSelectionHandleOne );
3171 if ( !mSelectionHandleTwo )
3173 // create normal and pressed images
3174 mSelectionHandleTwoImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
3175 mSelectionHandleTwoImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3177 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3178 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3179 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3180 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3181 mIsSelectionHandleTwoFlipped = false;
3182 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3184 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3185 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3186 mHandleTwoGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
3187 mHandleTwoGrabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
3188 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3190 mTapDetector.Attach( mHandleTwoGrabArea );
3191 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3193 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3195 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3197 mActiveLayer.Add( mSelectionHandleTwo );
3200 SetUpHandlePropertyNotifications();
3202 // update table as text may have changed.
3203 GetTextLayoutInfo();
3205 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
3206 bool altPositionValidOne; // Alternate cursor validity flag.
3207 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3208 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
3210 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
3211 bool altPositionValidTwo; // Alternate cursor validity flag.
3212 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3213 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
3215 // VCC TODO: This method is a hack for one line.
3216 ChooseRtlSelectionHandlePosition( cursorPositionOne,
3218 altPositionValidOne,
3219 altPositionValidTwo,
3223 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3224 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3226 // Calculates and set the visibility if the scroll mode is enabled.
3227 bool isSelectionHandleOneVisible = true;
3228 bool isSelectionHandleTwoVisible = true;
3229 if( IsScrollEnabled() )
3231 const Vector3& controlSize( GetControlSize() );
3232 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3233 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3234 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3235 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3238 CreateHighlight(); // function will only create highlight if not already created.
3241 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3243 Vector3 actualHandlePosition;
3245 if ( mSelectionHandleOne && mSelectionHandleTwo )
3247 const Vector3& controlSize = GetControlSize();
3249 Size cursorSize( CURSOR_THICKNESS, 0.f );
3251 // Get a reference of the wanted selection handle (handle one or two).
3252 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3254 // Get a reference for the current position of the handle and a copy of its pair
3255 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3256 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3258 // Get a handle of the selection handle actor
3259 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3261 // Selection handles should jump to the nearest character
3262 std::size_t newHandlePosition = 0;
3263 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3265 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
3266 bool altPositionValid; // Alternate cursor validity flag.
3267 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3268 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition, directionRTL, altPosition, altPositionValid );
3269 if( altPositionValid )
3271 // Check which of the positions is the closest.
3272 if( fabsf( altPosition.x - actualSelectionHandlePosition.x ) < fabsf( actualHandlePosition.x - actualSelectionHandlePosition.x ) )
3274 actualHandlePosition = altPosition;
3278 bool handleVisible = true;
3280 if( IsScrollEnabled() )
3282 mCurrentSelectionId = handleId;
3284 cursorSize.height = GetRowRectFromCharacterPosition( newHandlePosition ).height;
3285 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3286 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3293 mCurrentSelectionHandlePosition = actualHandlePosition;
3294 mScrollDisplacement = Vector2::ZERO;
3298 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3300 mScrollDisplacement.x = -SCROLL_SPEED;
3302 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3304 mScrollDisplacement.x = SCROLL_SPEED;
3306 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3308 mScrollDisplacement.y = -SCROLL_SPEED;
3310 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3312 mScrollDisplacement.y = SCROLL_SPEED;
3318 if ( handleVisible && // Ensure the handle is visible.
3319 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3320 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3322 currentSelectionHandlePosition = newHandlePosition;
3324 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3325 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3329 if ( handleId == HandleOne )
3331 const TextStyle oldInputStyle( mInputStyle );
3333 // Set Active Style to that of first character in selection
3334 if( mSelectionHandleOnePosition < mStyledText.size() )
3336 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3339 if( oldInputStyle != mInputStyle )
3341 // Updates the line height accordingly with the input style.
3344 EmitStyleChangedSignal();
3350 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3353 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3355 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3356 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3358 if ( selectionHandleActor )
3360 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3361 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3362 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3364 if( IsScrollEnabled() )
3366 const Size cursorSize( CURSOR_THICKNESS,
3367 GetRowRectFromCharacterPosition( selectionHandlePosition ).height );
3368 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3370 GetControlSize() ) );
3375 void TextInput::GetVisualTextSelection( std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection )
3377 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3379 // VCC Set true/false in logical order. TODO : It needs to be checked.
3381 if( startSelection > endSelection )
3383 std::swap( startSelection, endSelection );
3385 std::size_t index = 0u;
3386 for( std::vector<bool>::iterator it = selectedVisualText.begin(), endIt = selectedVisualText.end(); it != endIt; ++it, ++index )
3388 if( ( index < startSelection ) || ( endSelection <= index ) )
3399 // Calculate the dimensions of the quads they will make the highlight mesh
3400 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3402 // At the moment there is no public API to modify the block alignment option.
3404 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3406 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3408 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3409 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3411 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3412 std::vector<bool> selectedVisualText;
3413 GetVisualTextSelection( selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
3414 std::vector<bool>::iterator selectedIt = selectedVisualText.begin();
3415 std::vector<bool>::iterator selectedEndIt = selectedVisualText.end();
3417 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3418 float rowLeft = 0.0f;
3419 float rowRight = 0.0f;
3420 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3421 float maxRowLeft = std::numeric_limits<float>::max();
3422 float maxRowRight = 0.0f;
3424 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3426 // Scan through entire text.
3429 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3431 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3432 bool charSelected = false;
3433 if( selectedIt != selectedEndIt )
3435 charSelected = *selectedIt++;
3438 if( selectionState == SelectionNone )
3442 selectionState = SelectionStarted;
3443 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3444 rowRight = rowLeft + charInfo.mSize.width;
3447 else if( selectionState == SelectionStarted )
3449 // break selection on:
3450 // 1. new line causing selection break. (\n or wordwrap)
3451 // 2. character not selected.
3452 if( !charSelected ||
3453 ( charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ) )
3455 // finished selection.
3456 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3457 // that it resides on. That way this enumeration is not necessary.
3459 if(lastIt->mIsNewParagraphChar)
3461 // 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.
3462 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3464 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3465 maxRowLeft = std::min(maxRowLeft, min.x);
3466 maxRowRight = std::max(maxRowRight, max.x);
3467 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3468 float rowTop = rowBottom - rowSize.height;
3470 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3473 rowRight = std::numeric_limits<float>::max();
3475 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3477 selectionState = SelectionNone;
3479 // Still selected? start a new selection
3482 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3484 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3485 selectionState = SelectionStarted;
3490 // build up highlight(s) with this selection data.
3491 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3492 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3499 // If reached end, and still on selection, then close selection.
3502 if(selectionState == SelectionStarted)
3504 // finished selection.
3506 if(lastIt->mIsNewParagraphChar)
3508 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3510 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3511 maxRowLeft = std::min(maxRowLeft, min.x);
3512 maxRowRight = std::max(maxRowRight, max.x);
3513 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3514 float rowTop = rowBottom - rowSize.height;
3515 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3519 // Get the top left and bottom right corners.
3520 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3521 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3522 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3524 // Clamp quads so they appear to clip to borders of the whole text.
3525 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3527 // For block-align align Further Clamp quads to max left and right extents
3528 // BlockAlign: Will adjust highlight to block:
3530 // H[ello] (top row right = max of all rows right)
3531 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3532 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3533 // [text] (bottom row left = min of all rows left)
3534 // (common in SMS messaging selection)
3536 // As opposed to the default which is tight text highlighting.
3541 // (common in regular text editors/web browser selection)
3542 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3544 // Finally clamp quads again so they don't exceed the boundry of the control.
3545 const Vector3& controlSize = GetControlSize();
3546 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3549 return mNewHighlightInfo;
3552 // VCC TODO: two methods are not needed. this one is a quick hack to fix PLMs. Should implement one which support both directions.
3553 // This method creates one quad per character so different selection boxes for a mix of LTR and RTL languages are created.
3554 TextInput::HighlightInfo TextInput::CalculateHighlightInfoRtl()
3556 // At the moment there is no public API to modify the block alignment option.
3558 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3560 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3562 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3563 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3565 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3566 std::vector<bool> selectedVisualText;
3567 GetVisualTextSelection( selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
3568 std::vector<bool>::iterator selectedIt = selectedVisualText.begin();
3569 std::vector<bool>::iterator selectedEndIt = selectedVisualText.end();
3571 // SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3572 float rowLeft = 0.0f;
3573 float rowRight = 0.0f;
3575 // VCC TODO this is valid for one line.
3577 const Size rowSize = GetRowRectFromCharacterPosition( 0, min, max );
3579 // Scan through entire text.
3582 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3584 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3585 bool charSelected = false;
3586 if( selectedIt != selectedEndIt )
3588 charSelected = *selectedIt++;
3593 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3594 rowRight = rowLeft + charInfo.mSize.width;
3596 float rowBottom = charInfo.mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3597 float rowTop = rowBottom - rowSize.height;
3598 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3604 // Finally clamp quads again so they don't exceed the boundry of the control.
3605 const Vector3& controlSize = GetControlSize();
3606 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3609 return mNewHighlightInfo;
3612 void TextInput::UpdateHighlight()
3614 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3616 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3618 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3619 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3620 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3621 // [BOTTOM] [ MIDDLE ]
3624 // Each quad is created as 2 triangles.
3625 // Middle is just 1 quad regardless of its size.
3641 void TextInput::ClearPopup()
3643 mPopupPanel.Clear();
3646 void TextInput::AddPopupOptions()
3648 mPopupPanel.AddPopupOptions();
3651 void TextInput::SetPopupPosition( const Vector3& position, const Vector2& alternativePosition )
3653 const Vector3& visiblePopUpSize = mPopupPanel.GetVisibileSize();
3655 Vector3 clampedPosition ( position );
3656 Vector3 tailOffsetPosition ( position );
3658 float xOffSet( 0.0f );
3660 Actor self = Self();
3661 const Vector3 textViewTopLeftWorldPosition = self.GetCurrentWorldPosition() - self.GetCurrentSize()*0.5f;
3663 const float popUpLeft = textViewTopLeftWorldPosition.x + position.x - visiblePopUpSize.width*0.5f;
3664 const float popUpTop = textViewTopLeftWorldPosition.y + position.y - visiblePopUpSize.height;
3666 // Clamp to left or right or of boundary
3667 if( popUpLeft < mBoundingRectangleWorldCoordinates.x )
3669 xOffSet = mBoundingRectangleWorldCoordinates.x - popUpLeft ;
3671 else if ( popUpLeft + visiblePopUpSize.width > mBoundingRectangleWorldCoordinates.z )
3673 xOffSet = mBoundingRectangleWorldCoordinates.z - ( popUpLeft + visiblePopUpSize.width );
3676 clampedPosition.x = position.x + xOffSet;
3677 tailOffsetPosition.x = -xOffSet;
3679 // Check if top left of PopUp outside of top bounding rectangle, if so then flip to lower position.
3680 bool flipTail( false );
3682 if ( popUpTop < mBoundingRectangleWorldCoordinates.y )
3684 clampedPosition.y = alternativePosition.y + visiblePopUpSize.height;
3688 mPopupPanel.GetRootActor().SetPosition( clampedPosition );
3689 mPopupPanel.SetTailPosition( tailOffsetPosition, flipTail );
3692 void TextInput::HidePopup(bool animate, bool signalFinished )
3694 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
3696 mPopupPanel.Hide( animate );
3698 if( animate && signalFinished )
3700 mPopupPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3705 void TextInput::ShowPopup( bool animate )
3708 Vector2 alternativePopupPosition;
3710 if(false && mState == StateEdit)
3713 Vector3 bottomHandle; // referring to the bottom most point of the handle or the bottom line of selection.
3715 // When text is selected, show popup above top handle (and text), or below bottom handle.
3716 // topHandle: referring to the top most point of the handle or the top line of selection.
3717 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3719 topHandle = mSelectionHandleOneActualPosition;
3720 bottomHandle = mSelectionHandleTwoActualPosition;
3721 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3725 topHandle = mSelectionHandleTwoActualPosition;
3726 bottomHandle = mSelectionHandleOneActualPosition;
3727 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3729 topHandle.y += -mPopupOffsetFromText.y - rowSize.height;
3730 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3732 float xPosition = ( fabsf( topHandle.x - bottomHandle.x ) )*0.5f + std::min( mSelectionHandleOneActualPosition.x , mSelectionHandleTwoActualPosition.x );
3734 position.x = xPosition;
3736 // Alternative position if no upper space
3737 bottomHandle.y += GetSelectionHandleSize().y + mPopupOffsetFromText.w;
3738 alternativePopupPosition = Vector2 ( position.x, bottomHandle.y );
3742 // When no text is selected, show popup at world position of grab handle or cursor
3743 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3744 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3745 position.y -= ( mPopupOffsetFromText.y + rowSize.height );
3746 // if can't be positioned above, then position below row.
3747 alternativePopupPosition = Vector2( position.x, position.y ); // default if no grab handle
3750 // If grab handle enabled then position pop-up below the grab handle.
3751 alternativePopupPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + mPopupOffsetFromText.w +50.0f;
3755 SetPopupPosition( position, alternativePopupPosition );
3758 mPopupPanel.Show( Self(), animate );
3759 StartMonitoringStageForTouch();
3761 mPopupPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3764 void TextInput::ShowPopupCutCopyPaste()
3768 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3769 // Check the selected text is whole text or not.
3770 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3772 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3775 if ( !mStyledText.empty() && IsTextSelected() )
3777 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCopy, true );
3778 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, true );
3781 if( mClipboard && mClipboard.NumberOfItems() )
3783 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3784 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3789 mPopupPanel.Hide(false);
3793 void TextInput::SetUpPopupSelection( bool showCutButton )
3796 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3797 // If no text exists then don't offer to select
3798 if ( !mStyledText.empty() )
3800 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3801 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelect, true );
3802 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, ( showCutButton && IsTextSelected() ) );
3804 // if clipboard has valid contents then offer paste option
3805 if( mClipboard && mClipboard.NumberOfItems() )
3807 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3808 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3813 mPopupPanel.Hide(false);
3816 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3821 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3822 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3823 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3824 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3826 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3828 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3830 float closestYdifference = std::numeric_limits<float>::max();
3831 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3832 std::size_t numberOfMatchedCharacters = 0;
3834 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3835 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3837 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3839 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3840 float baselinePosition = info.mPosition.y - info.mDescender;
3842 if( info.mIsVisible )
3844 // store difference between source y point and the y position of the current character
3845 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3847 if( currentYdifference < closestYdifference )
3849 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3850 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3851 closestYdifference = currentYdifference;
3852 matchedCharacters.clear();
3853 numberOfMatchedCharacters = 0; // reset count
3856 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3857 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3859 // ignore new line character.
3860 if( !info.mIsNewParagraphChar )
3862 matchedCharacters.push_back( info );
3863 numberOfMatchedCharacters++;
3867 } // End of loop checking each character's y position in the character layout table
3869 // Check if last character is a newline, if it is
3870 // then need pretend there is an imaginary line afterwards,
3871 // and check if user is touching below previous line.
3872 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3874 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewParagraphChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3876 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3880 // 2 Iterate through matching list of y positions and find closest matching X position.
3882 bool matched( false );
3884 // Traverse the characters in the visual order. VCC TODO: check for more than one line.
3885 std::size_t visualIndex = 0u;
3886 const std::size_t matchedCharactersSize = matchedCharacters.size();
3887 for( ; visualIndex < matchedCharactersSize; ++visualIndex )
3889 const Toolkit::TextView::CharacterLayoutInfo& info( *( matchedCharacters.begin() + mTextLayoutInfo.mCharacterVisualToLogicalMap[visualIndex] ) );
3891 if( info.mIsVisible )
3893 // stop when on left side of character's center.
3894 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3895 if( sourceScrollOffset.x < characterMidPointPosition )
3897 if(info.mIsRightToLeftCharacter)
3899 rightToLeftChar = true;
3901 glyphIntersection = info.mPosition.x;
3906 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3910 if( visualIndex == matchedCharactersSize )
3912 rightToLeftChar = lastRightToLeftChar;
3915 closestIndex = lineOffset + visualIndex;
3917 mClosestCursorPositionEOL = false; // reset
3918 if( ( visualIndex == matchedCharactersSize ) && !matched )
3920 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3923 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3924 if( rightToLeftChar && lastRightToLeftChar )
3926 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3931 // closestIndex is the visual index, need to convert it to the logical index
3932 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3934 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3936 // Checks for situations where user is touching between LTR and RTL
3937 // characters. To identify if the user means the end of a LTR string
3938 // or the beginning of an RTL string, and vice versa.
3939 if( closestIndex > 0 )
3941 if( rightToLeftChar && !lastRightToLeftChar )
3946 // A: In this touch range, the user is indicating that they wish to place
3947 // the cursor at the end of the LTR text.
3948 // B: In this touch range, the user is indicating that they wish to place
3949 // the cursor at the end of the RTL text.
3951 // Result of touching A area:
3952 // [.....LTR]|[RTL......]+
3954 // |: primary cursor (for typing LTR chars)
3955 // +: secondary cursor (for typing RTL chars)
3957 // Result of touching B area:
3958 // [.....LTR]+[RTL......]|
3960 // |: primary cursor (for typing RTL chars)
3961 // +: secondary cursor (for typing LTR chars)
3963 if( sourceScrollOffset.x < glyphIntersection )
3968 else if( !rightToLeftChar && lastRightToLeftChar )
3970 if( sourceScrollOffset.x < glyphIntersection )
3977 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3978 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3979 // one further ahead
3980 if( rightToLeftChar && !lastRightToLeftChar )
3985 else if( closestIndex == std::numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3987 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3989 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3998 float TextInput::GetLineJustificationPosition() const
4000 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4001 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4002 float alignmentOffset = 0.f;
4004 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4005 if( alignment & Toolkit::Alignment::HorizontalLeft )
4007 alignmentOffset = 0.f;
4009 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4011 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4013 else if( alignment & Toolkit::Alignment::HorizontalRight )
4015 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4018 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4019 float justificationOffset = 0.f;
4021 switch( justification )
4023 case Toolkit::TextView::Left:
4025 justificationOffset = 0.f;
4028 case Toolkit::TextView::Center:
4030 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4033 case Toolkit::TextView::Right:
4035 justificationOffset = mTextLayoutInfo.mTextSize.width;
4038 case Toolkit::TextView::Justified:
4040 justificationOffset = 0.f;
4045 DALI_ASSERT_ALWAYS( false );
4049 return alignmentOffset + justificationOffset;
4052 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4054 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4055 A newline character is not inserted in this case */
4057 Vector3 cursorPosition;
4059 Toolkit::TextView::CharacterLayoutInfo currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4063 if( characterPosition > 0u )
4065 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1u ];
4067 // If previous character on a different line then use current characters position
4068 if( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4070 // VCC TODO: PositionCursorAfterWordWrap currently doesn't work for multiline. Need to check this branch.
4071 if ( mClosestCursorPositionEOL )
4073 cursorPosition = Vector3( previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z ) ;
4077 cursorPosition = Vector3( currentCharInfo.mPosition );
4086 // If the character is left to right, the position is the character's position plus its width.
4087 const float ltrOffset = !currentCharInfo.mIsRightToLeftCharacter ? currentCharInfo.mSize.width : 0.f;
4089 cursorPosition.x = currentCharInfo.mPosition.x + ltrOffset;
4090 cursorPosition.y = currentCharInfo.mPosition.y;
4093 return cursorPosition;
4096 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition ) const
4098 bool direction = false;
4099 Vector3 alternatePosition;
4100 bool alternatePositionValid = false;
4102 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4105 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4107 DALI_ASSERT_DEBUG( ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ) &&
4108 ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterVisualToLogicalMap.size() ) &&
4109 "TextInput::GetActualPositionFromCharacterPosition. All layout tables must have the same size." );
4111 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4113 alternatePositionValid = false;
4114 directionRTL = false;
4116 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4118 if( characterPosition == 0u )
4120 // When the cursor position is at the beginning, it should be at the start of the current character.
4121 // If the current character is LTR, then the start is on the right side of the glyph.
4122 // If the current character is RTL, then the start is on the left side of the glyph.
4124 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() ) ).mIsVisible )
4126 characterPosition = FindVisibleCharacter( Right, 0u );
4129 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4130 const float rtlOffset = info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f;
4132 cursorPosition.x = info.mPosition.x + rtlOffset;
4133 cursorPosition.y = info.mPosition.y;
4134 directionRTL = info.mIsRightToLeftCharacter;
4136 else if( characterPosition > 0u )
4138 // Get the direction of the paragraph.
4139 const std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition );
4140 const bool isParagraphRightToLeft = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + startCharacterPosition ) ).mIsRightToLeftCharacter;
4142 // When cursor is not at beginning, consider possibility of
4143 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4145 // Cursor position should be the end of the last character.
4146 // If the last character is LTR, then the end is on the right side of the glyph.
4147 // If the last character is RTL, then the end is on the left side of the glyph.
4149 --characterPosition;
4151 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition ) ).mIsVisible )
4153 characterPosition = FindVisibleCharacter( Left, characterPosition );
4156 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4157 if( ( characterPosition > 0u ) && info.mIsNewParagraphChar && !IsScrollEnabled() )
4159 // VCC TODO : check for a new paragraph character.
4161 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4162 const Vector3& size = GetControlSize();
4164 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4166 --characterPosition;
4168 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4171 if( !info.mIsNewParagraphChar )
4173 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4177 // VCC TODO : check for a new paragraph character.
4179 // When cursor points to first character on new line, position cursor at the start of this glyph.
4180 if( characterPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4182 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4183 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4185 cursorPosition.x = infoNext.mPosition.x + start;
4186 cursorPosition.y = infoNext.mPosition.y;
4190 // If cursor points to the end of text, then can only position
4191 // cursor where the new line starts based on the line-justification position.
4192 cursorPosition.x = GetLineJustificationPosition();
4194 if( characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() )
4196 // If this is after the last character, then we can assume that the new cursor
4197 // should be exactly one row below the current row.
4199 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition - 1u );
4200 cursorPosition.y = info.mPosition.y + rowRect.height;
4204 // If this is not after last character, then we can use this row's height.
4205 // should be exactly one row below the current row.
4207 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition );
4208 cursorPosition.y = info.mPosition.y + rowRect.height;
4213 directionRTL = info.mIsRightToLeftCharacter;
4215 if( 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4217 // 1. When the cursor is neither at the beginning or the end,
4218 // we can show multiple cursors under situations when the cursor is
4219 // between RTL and LTR text...
4220 if( characterPosition + 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4222 std::size_t characterAltPosition = characterPosition + 1u;
4224 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterAltPosition ];
4226 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4228 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4229 // Text: [...LTR...]|[...RTL...]
4231 // Alternate cursor pos: ^
4232 // In which case we need to display an alternate cursor for the RTL text.
4234 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4235 alternatePosition.y = infoAlt.mPosition.y;
4236 alternatePositionValid = true;
4238 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4240 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4241 // Text: |[...RTL...] [...LTR....]
4243 // Alternate cursor pos: ^
4244 // In which case we need to display an alternate cursor for the RTL text.
4246 alternatePosition.x = infoAlt.mPosition.x;
4247 alternatePosition.y = infoAlt.mPosition.y;
4248 alternatePositionValid = true;
4253 // 2. When the cursor is at the end of the text,
4254 // and we have multi-directional text,
4255 // we can also consider showing mulitple cursors.
4256 // The rule here is:
4257 // If first and last characters on row are different
4258 // Directions, then two cursors need to be displayed.
4260 if( info.mIsRightToLeftCharacter != isParagraphRightToLeft )
4262 // The last character's direction is differernt than the first one of current paragraph.
4265 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ GetFirstCharacterWithSameDirection( characterPosition ) ];
4267 if(info.mIsRightToLeftCharacter)
4269 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4270 // Text: [...LTR...]|[...RTL...]
4272 // Alternate cursor pos: ^
4273 // In which case we need to display an alternate cursor for the RTL text, this cursor
4274 // should be at the end of the given line.
4276 alternatePosition.x = infoStart.mPosition.x + infoStart.mSize.width;
4277 alternatePosition.y = infoStart.mPosition.y;
4278 alternatePositionValid = true;
4280 else if(!info.mIsRightToLeftCharacter) // starting RTL
4282 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4283 // Text: |[...RTL...] [...LTR....]
4285 // Alternate cursor pos: ^
4286 // In which case we need to display an alternate cursor for the RTL text.
4288 alternatePosition.x = infoStart.mPosition.x;
4289 alternatePosition.y = infoStart.mPosition.y;
4290 alternatePositionValid = true;
4295 } // characterPosition > 0
4299 // If the character table is void, place the cursor accordingly the text alignment.
4300 const Vector3& size = GetControlSize();
4302 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4303 float alignmentOffset = 0.f;
4305 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4306 if( alignment & Toolkit::Alignment::HorizontalLeft )
4308 alignmentOffset = 0.f;
4310 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4312 alignmentOffset = 0.5f * ( size.width );
4314 else if( alignment & Toolkit::Alignment::HorizontalRight )
4316 alignmentOffset = size.width;
4319 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4320 cursorPosition.x = alignmentOffset;
4322 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4323 if( alignment & Toolkit::Alignment::VerticalTop )
4325 cursorPosition.y = mLineHeight;
4327 else if( alignment & Toolkit::Alignment::VerticalCenter )
4329 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4331 else if( alignment & Toolkit::Alignment::VerticalBottom )
4333 cursorPosition.y = size.height;
4337 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4338 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4340 if( alternatePositionValid )
4342 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4343 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4346 return cursorPosition;
4349 std::size_t TextInput::GetRowStartFromCharacterPosition( std::size_t logicalPosition ) const
4351 // scan string from current position to beginning of current line to note direction of line
4352 while( logicalPosition )
4355 if( mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsNewParagraphChar )
4362 return logicalPosition;
4365 std::size_t TextInput::GetFirstCharacterWithSameDirection( std::size_t logicalPosition ) const
4367 const bool isRightToLeft = mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter;
4369 while( logicalPosition )
4372 if( isRightToLeft != mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter )
4379 return logicalPosition;
4382 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4386 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4389 Size TextInput::GetRowRectFromCharacterPosition( std::size_t characterPosition, Vector2& min, Vector2& max ) const
4391 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4392 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4394 min = Vector2::ZERO;
4395 max = Vector2(0.0f, mLineHeight);
4399 DALI_ASSERT_DEBUG( characterPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4401 // Initializes the min and max position.
4402 const std::size_t initialPosition = ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) ? characterPosition - 1u : characterPosition;
4403 min = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + initialPosition ) ).mPosition.GetVectorXY();
4407 // 1) Find the line where the character is laid-out.
4408 for( Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineIt = mTextLayoutInfo.mLines.begin(), lineEndIt = mTextLayoutInfo.mLines.end();
4409 !found && ( lineIt != mTextLayoutInfo.mLines.end() );
4412 const Toolkit::TextView::LineLayoutInfo& lineInfo( *lineIt );
4414 // Index within the whole text to the last character of the current line.
4415 std::size_t lastCharacterOfLine = 0u;
4417 Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineNextIt = lineIt + 1u;
4418 if( lineNextIt != lineEndIt )
4420 lastCharacterOfLine = (*lineNextIt).mCharacterGlobalIndex - 1u;
4424 lastCharacterOfLine = mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1u;
4427 // Check if the given chracter position is within the line.
4428 if( ( lineInfo.mCharacterGlobalIndex <= initialPosition ) && ( initialPosition <= lastCharacterOfLine ) )
4430 // 2) Get the row rect of all laid-out characters on the line.
4432 // Need to scan all characters of the line because they are in the logical position.
4433 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lineInfo.mCharacterGlobalIndex,
4434 endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lastCharacterOfLine + 1u;
4438 const Toolkit::TextView::CharacterLayoutInfo& characterInfo( *it );
4440 min.x = std::min( min.x, characterInfo.mPosition.x );
4441 min.y = std::min( min.y, characterInfo.mPosition.y );
4442 max.x = std::max( max.x, characterInfo.mPosition.x + characterInfo.mSize.width );
4443 max.y = std::max( max.y, characterInfo.mPosition.y + characterInfo.mSize.height );
4450 return Size( max.x - min.x, max.y - min.y );
4453 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4455 Actor popUpPanel = mPopupPanel.GetRootActor();
4457 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4463 Dali::Actor parent( touchedActor.GetParent() );
4467 return WasTouchedCheck( parent );
4474 void TextInput::StartMonitoringStageForTouch()
4476 Stage stage = Stage::GetCurrent();
4477 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4480 void TextInput::EndMonitoringStageForTouch()
4482 Stage stage = Stage::GetCurrent();
4483 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4486 void TextInput::OnStageTouched(const TouchEvent& event)
4488 if( event.GetPointCount() > 0 )
4490 if ( TouchPoint::Down == event.GetPoint(0).state )
4492 const Actor touchedActor(event.GetPoint(0).hitActor);
4494 bool popUpShown( false );
4496 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
4501 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4503 if ( ( false || popUpShown ) && !textInputTouched )
4505 EndMonitoringStageForTouch();
4506 HidePopup( true, false );
4509 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4511 EndMonitoringStageForTouch();
4512 ShowGrabHandleAndSetVisibility( false );
4518 void TextInput::SelectText(std::size_t start, std::size_t end)
4520 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4521 IsGrabHandleEnabled()?"true":"false",
4522 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4523 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4524 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4526 StartMonitoringStageForTouch();
4528 if ( mEditModeActive ) // Only allow text selection when in edit mode
4530 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4531 mSelectingText = true;
4533 std::size_t selectionStartPosition = std::min( start, end );
4535 // Hide grab handle when selecting.
4536 ShowGrabHandleAndSetVisibility( false );
4538 if( start != end ) // something to select
4540 SetCursorVisibility( false );
4541 StopCursorBlinkTimer();
4543 CreateSelectionHandles(start, end);
4546 const TextStyle oldInputStyle( mInputStyle );
4547 mInputStyle = GetStyleAt( selectionStartPosition ); // Inherit style from selected position.
4549 if( oldInputStyle != mInputStyle )
4551 // Updates the line height accordingly with the input style.
4554 EmitStyleChangedSignal();
4560 mSelectingText = false;
4564 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4566 MarkupProcessor::StyledTextArray currentSelectedText;
4568 if ( IsTextSelected() )
4570 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4571 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4573 for(; it != end; ++it)
4575 MarkupProcessor::StyledText& styledText( *it );
4576 currentSelectedText.push_back( styledText );
4579 return currentSelectedText;
4582 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4584 const std::size_t beginIndex = std::min( begin, end );
4585 const std::size_t endIndex = std::max( begin, end );
4588 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4590 // Create a styled text array used to replace the text into the text-view.
4591 MarkupProcessor::StyledTextArray text;
4592 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4594 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4595 GetTextLayoutInfo();
4597 if( IsScrollEnabled() )
4599 // Need to set the scroll position as the text's size may have changed.
4600 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4603 ShowGrabHandleAndSetVisibility( false );
4609 // Set Handle positioning as the new style may have repositioned the characters.
4610 SetSelectionHandlePosition(HandleOne);
4611 SetSelectionHandlePosition(HandleTwo);
4614 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4616 // Just hide the grab handle when keyboard is hidden.
4617 if (!keyboardShown )
4619 ShowGrabHandleAndSetVisibility( false );
4621 // If the keyboard is not now being shown, then hide the popup panel
4622 mPopupPanel.Hide( true );
4626 // Removes highlight and resumes edit mode state
4627 void TextInput::RemoveHighlight( bool hidePopup )
4629 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4633 if ( mSelectionHandleOne )
4635 mActiveLayer.Remove( mSelectionHandleOne );
4636 mSelectionHandleOne.Reset();
4637 mSelectionHandleOneOffset.x = 0.0f;
4639 if ( mSelectionHandleTwo )
4641 mActiveLayer.Remove( mSelectionHandleTwo );
4642 mSelectionHandleTwo.Reset();
4643 mSelectionHandleTwoOffset.x = 0.0f;
4646 mNewHighlightInfo.mQuadList.clear();
4649 SetCursorVisibility( true );
4650 StartCursorBlinkTimer();
4652 // NOTE: We cannot dereference mHighlightMesh, due
4653 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4661 mSelectionHandleOnePosition = 0;
4662 mSelectionHandleTwoPosition = 0;
4665 void TextInput::CreateHighlight()
4670 bool TextInput::CopySelectedTextToClipboard()
4672 mCurrentCopySelecton.clear();
4674 mCurrentCopySelecton = GetSelectedText();
4676 std::string stringToStore;
4678 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4679 * a marked up string.
4681 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4682 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4684 bool success = mClipboard.SetItem( stringToStore );
4688 void TextInput::PasteText( const Text& text )
4690 // Update Flag, indicates whether to update the text-input contents or not.
4691 // Any key stroke that results in a visual change of the text-input should
4692 // set this flag to true.
4693 bool update = false;
4696 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4697 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4699 ImfManager imfManager = ImfManager::Get();
4702 imfManager.SetCursorPosition( mCursorPosition );
4703 imfManager.NotifyCursorPosition();
4705 DeleteHighlightedText( true );
4709 bool textExceedsMaximunNumberOfCharacters = false;
4710 bool textExceedsBoundary = false;
4712 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4714 mCursorPosition += insertedStringLength;
4715 ImfManager imfManager = ImfManager::Get();
4718 imfManager.SetCursorPosition ( mCursorPosition );
4719 imfManager.NotifyCursorPosition();
4722 update = update || ( insertedStringLength > 0 );
4729 if( insertedStringLength < text.GetLength() )
4731 EmitMaxInputCharactersReachedSignal();
4734 if( textExceedsBoundary )
4736 EmitInputTextExceedsBoundariesSignal();
4740 void TextInput::SetTextDirection()
4742 // Put the cursor to the right if we are empty and an RTL language is being used.
4743 if ( mStyledText.empty() )
4745 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4747 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4748 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4750 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4751 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4753 int alignment( mDisplayedTextView.GetTextAlignment() &
4754 ( Toolkit::Alignment::VerticalTop |
4755 Toolkit::Alignment::VerticalCenter |
4756 Toolkit::Alignment::VerticalBottom |
4757 Toolkit::Alignment::HorizontalCenter ) );
4758 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4760 // If our alignment is in the center, then do not change.
4761 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4763 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4766 // If our justification is in the center, then do not change.
4767 if ( justification != Toolkit::TextView::Center )
4769 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4772 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4773 mDisplayedTextView.SetLineJustification( justification );
4777 void TextInput::UpdateLineHeight()
4779 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4780 mLineHeight = font.GetLineHeight();
4782 // 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.
4784 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4786 if( !mExceedEnabled || shrink )
4788 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4792 std::size_t TextInput::FindVisibleCharacter( FindVisibleCharacterDirection direction , std::size_t cursorPosition ) const
4794 // VCC check if we need do this in the visual order ...
4795 std::size_t position = 0u;
4797 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4803 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4805 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4807 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4813 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4814 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4816 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4822 position = FindVisibleCharacterLeft( 0u, mTextLayoutInfo.mCharacterLayoutInfoTable );
4827 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4834 void TextInput::SetSortModifier( float depthOffset )
4836 if(mDisplayedTextView)
4838 mDisplayedTextView.SetSortModifier(depthOffset);
4842 void TextInput::SetSnapshotModeEnabled( bool enable )
4844 if(mDisplayedTextView)
4846 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4850 bool TextInput::IsSnapshotModeEnabled() const
4852 bool snapshotEnabled = false;
4854 if(mDisplayedTextView)
4856 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4859 return snapshotEnabled;
4862 void TextInput::SetMarkupProcessingEnabled( bool enable )
4864 mMarkUpEnabled = enable;
4867 bool TextInput::IsMarkupProcessingEnabled() const
4869 return mMarkUpEnabled;
4872 void TextInput::SetScrollEnabled( bool enable )
4874 if( mDisplayedTextView )
4876 mDisplayedTextView.SetScrollEnabled( enable );
4881 // Don't set cursor's and handle's visibility to false if they are outside the
4882 // boundaries of the text-input.
4883 mIsCursorInScrollArea = true;
4884 mIsGrabHandleInScrollArea = true;
4885 if( mSelectionHandleOne && mSelectionHandleTwo )
4887 mSelectionHandleOne.SetVisible( true );
4888 mSelectionHandleTwo.SetVisible( true );
4894 bool TextInput::IsScrollEnabled() const
4896 bool scrollEnabled = false;
4898 if( mDisplayedTextView )
4900 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4903 return scrollEnabled;
4906 void TextInput::SetScrollPosition( const Vector2& position )
4908 if( mDisplayedTextView )
4910 mDisplayedTextView.SetScrollPosition( position );
4914 Vector2 TextInput::GetScrollPosition() const
4916 Vector2 scrollPosition;
4918 if( mDisplayedTextView )
4920 scrollPosition = mDisplayedTextView.GetScrollPosition();
4923 return scrollPosition;
4926 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4928 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4929 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4930 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4932 // Add style to the new input text.
4933 MarkupProcessor::StyledTextArray textToInsert;
4934 for( std::size_t i = 0; i < insertedStringLength; ++i )
4936 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4937 textToInsert.push_back( newStyledCharacter );
4940 //Insert text to the TextView.
4941 const bool emptyTextView = mStyledText.empty();
4942 if( emptyTextView && mPlaceHolderSet )
4944 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4945 mDisplayedTextView.SetText( textToInsert );
4949 if( 0 == numberOfCharactersToReplace )
4951 mDisplayedTextView.InsertTextAt( position, textToInsert );
4955 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4958 mPlaceHolderSet = false;
4960 if( textToInsert.empty() )
4962 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4963 GetTextLayoutInfo();
4967 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4968 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4971 textExceedsBoundary = false;
4973 if( !mExceedEnabled )
4975 const Vector3& size = GetControlSize();
4977 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4979 // If new text does not fit within TextView
4980 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4981 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4982 GetTextLayoutInfo();
4983 textExceedsBoundary = true;
4984 insertedStringLength = 0;
4987 if( textExceedsBoundary )
4989 // Add the part of the text which fits on the text-input.
4991 // Split the text which doesn't fit in two halves.
4992 MarkupProcessor::StyledTextArray firstHalf;
4993 MarkupProcessor::StyledTextArray secondHalf;
4994 SplitText( textToInsert, firstHalf, secondHalf );
4996 // Clear text. This text will be filled with the text inserted.
4997 textToInsert.clear();
4999 // Where to insert the text.
5000 std::size_t positionToInsert = position;
5002 bool end = text.GetLength() <= 1;
5005 // Insert text and check ...
5006 const std::size_t textLength = firstHalf.size();
5007 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5008 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5010 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5012 // Inserted text doesn't fit.
5014 // Remove inserted text
5015 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5016 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5018 // The iteration finishes when only one character doesn't fit.
5019 end = textLength <= 1;
5023 // Prepare next two halves for next iteration.
5024 MarkupProcessor::StyledTextArray copyText = firstHalf;
5025 SplitText( copyText, firstHalf, secondHalf );
5032 // store text to be inserted in mStyledText.
5033 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5035 // Increase the inserted characters counter.
5036 insertedStringLength += textLength;
5038 // Prepare next two halves for next iteration.
5039 MarkupProcessor::StyledTextArray copyText = secondHalf;
5040 SplitText( copyText, firstHalf, secondHalf );
5042 // Update where next text has to be inserted
5043 positionToInsert += textLength;
5049 if( textToInsert.empty() && emptyTextView )
5051 // No character has been added and the text-view was empty.
5052 // Show the placeholder text.
5053 ShowPlaceholderText( mStyledPlaceHolderText );
5057 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5058 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5059 mPlaceHolderSet = false;
5062 return insertedStringLength;
5065 void TextInput::GetTextLayoutInfo()
5067 if( mStyledText.empty() )
5069 // The text-input has no text, clear the text-view's layout info.
5070 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5074 if( mDisplayedTextView )
5076 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5080 // There is no text-view.
5081 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5086 void TextInput::SetOffsetFromText( const Vector4& offset )
5088 mPopupOffsetFromText = offset;
5091 const Vector4& TextInput::GetOffsetFromText() const
5093 return mPopupOffsetFromText;
5096 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5098 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5102 TextInput& textInputImpl( GetImpl( textInput ) );
5104 switch ( propertyIndex )
5106 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5108 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5111 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5113 textInputImpl.mPopupPanel.SetCutPastePopupColor( value.Get< Vector4 >() );
5116 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5118 textInputImpl.mPopupPanel.SetCutPastePopupPressedColor( value.Get< Vector4 >() );
5121 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY:
5123 textInputImpl.mPopupPanel.SetCutPastePopupBorderColor( value.Get< Vector4 >() );
5126 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5128 textInputImpl.mPopupPanel.SetCutPastePopupIconColor( value.Get< Vector4 >() );
5131 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5133 textInputImpl.mPopupPanel.SetCutPastePopupIconPressedColor( value.Get< Vector4 >() );
5136 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5138 textInputImpl.mPopupPanel.SetCutPastePopupTextColor( value.Get< Vector4 >() );
5141 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5143 textInputImpl.mPopupPanel.SetCutPastePopupTextPressedColor( value.Get< Vector4 >() );
5146 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5148 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5151 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5153 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5156 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5158 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5161 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5163 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5166 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5168 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5171 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5173 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5176 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5178 textInputImpl.SetOffsetFromText( value.Get< Vector4 >() );
5181 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5183 textInputImpl.mCursor.SetColor( value.Get< Vector4 >() );
5189 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5191 Property::Value value;
5193 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5197 TextInput& textInputImpl( GetImpl( textInput ) );
5199 switch ( propertyIndex )
5201 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5203 value = textInputImpl.GetMaterialDiffuseColor();
5206 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5208 value = textInputImpl.mPopupPanel.GetCutPastePopupColor();
5211 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5213 value = textInputImpl.mPopupPanel.GetCutPastePopupPressedColor();
5216 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY :
5218 value = textInputImpl.mPopupPanel.GetCutPastePopupBorderColor();
5221 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5223 value = textInputImpl.mPopupPanel.GetCutPastePopupIconColor();
5226 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5228 value = textInputImpl.mPopupPanel.GetCutPastePopupIconPressedColor();
5231 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5233 value = textInputImpl.mPopupPanel.GetCutPastePopupTextColor();
5236 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5238 value = textInputImpl.mPopupPanel.GetCutPastePopupTextPressedColor();
5241 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5243 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5246 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5248 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5251 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5253 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5256 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5258 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5261 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5263 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5266 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5268 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5271 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5273 value = textInputImpl.GetOffsetFromText();
5276 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5278 value = textInputImpl.mCursor.GetCurrentColor();
5285 void TextInput::EmitStyleChangedSignal()
5287 // emit signal if input style changes.
5288 Toolkit::TextInput handle( GetOwner() );
5289 mStyleChangedSignal.Emit( handle, mInputStyle );
5292 void TextInput::EmitTextModified()
5294 // emit signal when text changes.
5295 Toolkit::TextInput handle( GetOwner() );
5296 mTextModifiedSignal.Emit( handle );
5300 void TextInput::EmitMaxInputCharactersReachedSignal()
5302 // emit signal if max characters is reached during text input.
5303 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5305 Toolkit::TextInput handle( GetOwner() );
5306 mMaxInputCharactersReachedSignal.Emit( handle );
5309 void TextInput::EmitInputTextExceedsBoundariesSignal()
5311 // Emit a signal when the input text exceeds the boundaries of the text input.
5313 Toolkit::TextInput handle( GetOwner() );
5314 mInputTextExceedBoundariesSignal.Emit( handle );
5317 } // namespace Internal
5319 } // namespace Toolkit