New size negotiation
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-input / text-input-impl.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <math.h>
23 #include <sstream>
24 #include <algorithm>
25 #include <dali/public-api/adaptor-framework/virtual-keyboard.h>
26 #include <dali/public-api/animation/constraints.h>
27 #include <dali/public-api/common/stage.h>
28 #include <dali/public-api/events/key-event.h>
29 #include <dali/public-api/events/touch-event.h>
30 #include <dali/public-api/object/type-registry.h>
31 #include <dali/public-api/object/type-registry-helper.h>
32 #include <dali/public-api/object/property-notification.h>
33 #include <dali/public-api/size-negotiation/relayout-container.h>
34 #include <dali/integration-api/debug.h>
35 #include <dali/public-api/images/resource-image.h>
36
37 // INTERNAL INCLUDES
38 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
39 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
40 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
41 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
42
43 using namespace Dali;
44
45 // Local Data
46 namespace
47 {
48
49 #if defined(DEBUG_ENABLED)
50 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
51 #endif
52
53 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
54 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
55 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f );  // Selection cursor image size
56 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
57 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
58 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f );    // Used for Selection highlight
59
60 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
61 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
62 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
63 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
64 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
65
66 const std::size_t CURSOR_BLINK_INTERVAL = 500;                              ///< Cursor blink interval
67 const float CHARACTER_THRESHOLD( 2.5f );                                    ///< the threshold of a line.
68 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f );                           ///< 1. Highlight rendered (z-offset).
69 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f );                           ///< 2. Text rendered (z-offset).
70 const float UI_Z_OFFSET( 0.2f );                                            ///< 3. Text Selection Handles/Cursor z-offset.
71
72 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET);                           ///< Text Selection Handles/Cursor offset.
73 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f);                 ///< Handle One's Offset
74 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f);                 ///< Handle Two's Offset
75 const float TOP_HANDLE_TOP_OFFSET( 34.0f);                                  ///< Offset between top handle and cutCopyPaste pop-up
76 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f);                             ///< Offset between bottom handle and cutCopyPaste pop-up
77 const float CURSOR_THICKNESS(4.0f);
78 const Degree CURSOR_ANGLE_OFFSET(2.0f);                                     ///< Offset from the angle of italic angle.
79 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
80
81 const char* const NEWLINE = "\n";
82
83 const TextStyle DEFAULT_TEXT_STYLE;
84
85 const unsigned int SCROLL_TICK_INTERVAL = 50u;
86 const float SCROLL_THRESHOLD = 10.f;
87 const float SCROLL_SPEED = 15.f;
88
89 /**
90  * Selection state enumeration (FSM)
91  */
92 enum SelectionState
93 {
94   SelectionNone,                            ///< Currently not encountered selected section.
95   SelectionStarted,                         ///< Encountered selected section
96   SelectionFinished                         ///< Finished selected section
97 };
98
99 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
100 {
101   for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
102        it != endIt;
103        ++it )
104   {
105     if( ( *it ).mIsVisible )
106     {
107       return --cursorPosition;
108     }
109
110     --cursorPosition;
111   }
112
113   return 0u;
114 }
115
116 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable  )
117 {
118   for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
119   {
120     if( ( *it ).mIsVisible )
121     {
122       return cursorPosition;
123     }
124
125     ++cursorPosition;
126   }
127
128   return cursorPosition;
129 }
130
131 /**
132  * Whether the given position plus the cursor size offset is inside the given boundary.
133  *
134  * @param[in] position The given position.
135  * @param[in] cursorSize The cursor size.
136  * @param[in] controlSize The given boundary.
137  *
138  * @return whether the given position is inside the given boundary.
139  */
140 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
141 {
142   return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
143          ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
144          ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
145          ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
146 }
147
148 /**
149  * Splits a text in two halves.
150  *
151  * If the text's number of characters is odd, firstHalf has one more character.
152  *
153  * @param[in] text The text to be split.
154  * @param[out] firstHalf The first half of the text.
155  * @param[out] secondHalf The second half of the text.
156  */
157 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
158                       Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
159                       Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
160 {
161   firstHalf.clear();
162   secondHalf.clear();
163
164   const std::size_t textLength = text.size();
165   const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
166
167   firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
168   secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
169 }
170
171 } // end of namespace
172
173 namespace Dali
174 {
175
176 namespace Toolkit
177 {
178
179 namespace Internal
180 {
181
182 namespace // Unnamed namespace
183 {
184
185 BaseHandle Create()
186 {
187   return Toolkit::TextInput::New();
188 }
189
190 // Setup properties, signals and actions using the type-registry.
191 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::TextInput, Toolkit::Control, Create )
192
193 DALI_PROPERTY_REGISTRATION( TextInput, "highlight-color",                     VECTOR4,          HIGHLIGHT_COLOR                     )
194 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-bg-color",              VECTOR4,          CUT_AND_PASTE_COLOR                 )
195 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-pressed-color",         VECTOR4,          CUT_AND_PASTE_PRESSED_COLOR         )
196 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-border-color",          VECTOR4,          CUT_AND_PASTE_BORDER_COLOR          )
197 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-icon-color",            VECTOR4,          CUT_AND_PASTE_ICON_COLOR            )
198 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-icon-pressed-color",    VECTOR4,          CUT_AND_PASTE_ICON_PRESSED_COLOR    )
199 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-text-color",            VECTOR4,          CUT_AND_PASTE_TEXT_COLOR            )
200 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-text-pressed-color",    VECTOR4,          CUT_AND_PASTE_TEXT_PRESSED_COLOR    )
201 DALI_PROPERTY_REGISTRATION( TextInput, "cut-button-position-priority",        UNSIGNED_INTEGER, CUT_BUTTON_POSITION_PRIORITY        )
202 DALI_PROPERTY_REGISTRATION( TextInput, "copy-button-position-priority",       UNSIGNED_INTEGER, COPY_BUTTON_POSITION_PRIORITY       )
203 DALI_PROPERTY_REGISTRATION( TextInput, "paste-button-position-priority",      UNSIGNED_INTEGER, PASTE_BUTTON_POSITION_PRIORITY      )
204 DALI_PROPERTY_REGISTRATION( TextInput, "select-button-position-priority",     UNSIGNED_INTEGER, SELECT_BUTTON_POSITION_PRIORITY     )
205 DALI_PROPERTY_REGISTRATION( TextInput, "select-all-button-position-priority", UNSIGNED_INTEGER, SELECT_ALL_BUTTON_POSITION_PRIORITY )
206 DALI_PROPERTY_REGISTRATION( TextInput, "clipboard-button-position-priority",  UNSIGNED_INTEGER, CLIPBOARD_BUTTON_POSITION_PRIORITY  )
207 DALI_PROPERTY_REGISTRATION( TextInput, "popup-offset-from-text",              VECTOR4,          POP_UP_OFFSET_FROM_TEXT             )
208 DALI_PROPERTY_REGISTRATION( TextInput, "cursor-color",                        VECTOR4,          CURSOR_COLOR                        )
209
210 DALI_SIGNAL_REGISTRATION(   TextInput, "start-input",                                           SIGNAL_START_INPUT                  )
211 DALI_SIGNAL_REGISTRATION(   TextInput, "end-input",                                             SIGNAL_END_INPUT                    )
212 DALI_SIGNAL_REGISTRATION(   TextInput, "style-changed",                                         SIGNAL_STYLE_CHANGED                )
213 DALI_SIGNAL_REGISTRATION(   TextInput, "max-input-characters-reached",                          SIGNAL_MAX_INPUT_CHARACTERS_REACHED )
214 DALI_SIGNAL_REGISTRATION(   TextInput, "toolbar-displayed",                                     SIGNAL_TOOLBAR_DISPLAYED            )
215 DALI_SIGNAL_REGISTRATION(   TextInput, "text-exceed-boundaries",                                SIGNAL_TEXT_EXCEED_BOUNDARIES       )
216
217 DALI_TYPE_REGISTRATION_END()
218
219 }
220
221 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
222
223 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
224 {
225   QuadCoordinates quad(x1, y1, x2, y2);
226   mQuadList.push_back( quad );
227 }
228
229 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
230 {
231   for(std::size_t i = 0;i < mQuadList.size(); i++)
232   {
233     QuadCoordinates& quad = mQuadList[i];
234
235     quad.min.Clamp(min, max);
236     quad.max.Clamp(min, max);
237   } // end for
238 }
239
240 // [TextInput] ////////////////////////////////////////////////////////////////
241
242 Dali::Toolkit::TextInput TextInput::New()
243 {
244   // Create the implementation
245   TextInputPtr textInput(new TextInput());
246   // Pass ownership to CustomActor via derived handle
247   Dali::Toolkit::TextInput handle(*textInput);
248   handle.SetName( "TextInput");
249
250   textInput->Initialize();
251   return handle;
252 }
253
254 TextInput::TextInput()
255 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
256  mState( StateEdit ),
257  mStyledText(),
258  mInputStyle(),
259  mLineHeight( 0.f ),
260  mDisplayedTextView(),
261  mStyledPlaceHolderText(),
262  mMaxStringLength( DEFAULT_MAX_SIZE ),
263  mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
264  mCursorPosition( 0 ),
265  mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
266  mIsSelectionHandleOneFlipped( false ),
267  mIsSelectionHandleTwoFlipped( false ),
268  mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
269  mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
270  mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
271  mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
272  mSelectionHandleOnePosition( 0 ),
273  mSelectionHandleTwoPosition( 0 ),
274  mPreEditString(),
275  mPreEditStartPosition( 0 ),
276  mPreEditLength ( 0 ),
277  mNumberOfSurroundingCharactersDeleted( 0 ),
278  mTouchStartTime( 0 ),
279  mTextLayoutInfo(),
280  mCurrentCopySelecton(),
281  mPopupPanel(),
282  mScrollTimer(),
283  mScrollDisplacement(),
284  mCurrentHandlePosition(),
285  mCurrentSelectionId(),
286  mCurrentSelectionHandlePosition(),
287  mRequestedSelection( 0, 0 ),
288  mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
289  mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
290  mClipboard(),
291  mMaterialColor( LIGHTBLUE ),
292  mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
293  mOverrideAutomaticAlignment( false ),
294  mCursorRTLEnabled( false ),
295  mClosestCursorPositionEOL ( false ),
296  mCursorBlinkStatus( true ),
297  mCursorVisibility( false ),
298  mGrabHandleVisibility( false ),
299  mIsCursorInScrollArea( true ),
300  mIsGrabHandleInScrollArea( true ),
301  mEditModeActive( false ),
302  mEditOnTouch( true ),
303  mTextSelection( true ),
304  mExceedEnabled( true ),
305  mGrabHandleEnabled( true ),
306  mIsSelectionHandleFlipEnabled( true ),
307  mPreEditFlag( false ),
308  mIgnoreCommitFlag( false ),
309  mIgnoreFirstCommitFlag( false ),
310  mSelectingText( false ),
311  mPreserveCursorPosition( false ),
312  mSelectTextOnCommit( false ),
313  mUnderlinedPriorToPreEdit ( false ),
314  mCommitByKeyInput( false ),
315  mPlaceHolderSet( false ),
316  mMarkUpEnabled( false )
317 {
318   // Updates the line height accordingly with the input style.
319   UpdateLineHeight();
320 }
321
322 TextInput::~TextInput()
323 {
324   StopCursorBlinkTimer();
325 }
326
327 // Public
328
329 std::string TextInput::GetText() const
330 {
331   std::string text;
332
333   // Return text-view's text only if the text-input's text is not empty
334   // in order to not to return the placeholder text.
335   if( !mStyledText.empty() )
336   {
337     text = mDisplayedTextView.GetText();
338   }
339
340   return text;
341 }
342
343 std::string TextInput::GetMarkupText() const
344 {
345   std::string markupString;
346   MarkupProcessor::GetMarkupString( mStyledText, markupString );
347
348   return markupString;
349 }
350
351 void TextInput::ShowPlaceholderText( const MarkupProcessor::StyledTextArray& stylePlaceHolderText )
352 {
353   mDisplayedTextView.SetText( stylePlaceHolderText );
354   mPlaceHolderSet = true;
355   mDisplayedTextView.SetScrollPosition( Vector2( 0.0f,0.0f ) );
356 }
357
358 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
359 {
360   // Get the placeholder styled text array from the markup string.
361   MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
362   if( mStyledText.empty() )
363   {
364     ShowPlaceholderText( mStyledPlaceHolderText );
365   }
366 }
367
368 std::string TextInput::GetPlaceholderText()
369 {
370   // Traverses the styled placeholder array getting only the text.
371   //  Note that for some languages a 'character' could be represented by more than one 'char'
372
373   std::string placeholderText;
374   for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
375   {
376     placeholderText.append( (*it).mText.GetText() );
377   }
378
379   return placeholderText ;
380 }
381
382 void TextInput::SetInitialText(const std::string& initialText)
383 {
384   DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
385
386   if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
387   {
388     mPreEditFlag = false;
389     mIgnoreCommitFlag = true;
390   }
391
392   SetText( initialText );
393   PreEditReset( false ); // Reset keyboard as text changed
394 }
395
396 void TextInput::SetText(const std::string& initialText)
397 {
398   DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
399
400   GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
401
402   if( mStyledText.empty() )
403   {
404     ShowPlaceholderText( mStyledPlaceHolderText );
405   }
406   else
407   {
408     mDisplayedTextView.SetText( mStyledText );
409     mPlaceHolderSet = false;
410   }
411
412   GetTextLayoutInfo();
413
414   mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
415
416   ImfManager imfManager = ImfManager::Get();
417   if ( imfManager )
418   {
419     imfManager.SetCursorPosition( mCursorPosition );
420     imfManager.SetSurroundingText( initialText );
421     imfManager.NotifyCursorPosition();
422   }
423
424   if( IsScrollEnabled() )
425   {
426     ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
427   }
428
429   ShowGrabHandleAndSetVisibility( false );
430
431   RemoveHighlight();
432
433   DrawCursor();
434
435   EmitTextModified();
436 }
437
438 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
439 {
440   DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
441
442   mDisplayedTextView.SetText( styleText );
443   mPlaceHolderSet = false;
444
445   // If text alignment hasn't been manually set by application developer, then we
446   // automatically determine the alignment based on the content of the text i.e. what
447   // language the text begins with.
448   // TODO: This should determine different alignments for each line (broken by '\n') of text.
449   if(!mOverrideAutomaticAlignment)
450   {
451     // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
452     bool leftToRight(true);
453
454     if( !styleText.empty() )
455     {
456       bool breakOut(false);
457
458       for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
459       {
460         const Text& text = textIter->mText;
461
462         for( std::size_t i = 0; i < text.GetLength(); ++i )
463         {
464           Character character( text[i] );
465           if( character.GetCharacterDirection() != Character::Neutral )
466           {
467             leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
468             breakOut = true;
469             break;
470           }
471         }
472       }
473     }
474
475     // Based on this direction, either left or right align text if not manually set by application developer.
476     mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
477                                            ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
478                                              Toolkit::Alignment::VerticalTop ) );
479     mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
480   }
481
482   EmitTextModified();
483 }
484
485 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
486 {
487   mMaxStringLength = maxChars;
488 }
489
490 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
491 {
492   DALI_ASSERT_DEBUG( maxLines > 0 )
493
494   if ( maxLines > 0)
495   {
496     mNumberOflinesLimit = maxLines;
497   }
498 }
499
500 std::size_t TextInput::GetNumberOfLinesLimit() const
501 {
502   return mNumberOflinesLimit;
503 }
504
505 std::size_t TextInput::GetNumberOfCharacters() const
506 {
507   return mStyledText.size();
508 }
509
510 // Styling
511 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
512 {
513   mMaterialColor = color;
514   if ( mCustomMaterial )
515   {
516     mCustomMaterial.SetDiffuseColor( mMaterialColor );
517     mMeshData.SetMaterial( mCustomMaterial );
518   }
519 }
520
521 const Vector4& TextInput::GetMaterialDiffuseColor() const
522 {
523   return mMaterialColor;
524 }
525
526 // Signals
527
528 Toolkit::TextInput::InputSignalType& TextInput::InputStartedSignal()
529 {
530   return mInputStartedSignal;
531 }
532
533 Toolkit::TextInput::InputSignalType& TextInput::InputFinishedSignal()
534 {
535   return mInputFinishedSignal;
536 }
537
538 Toolkit::TextInput::InputSignalType& TextInput::CutAndPasteToolBarDisplayedSignal()
539 {
540   return mCutAndPasteToolBarDisplayed;
541 }
542
543 Toolkit::TextInput::StyleChangedSignalType& TextInput::StyleChangedSignal()
544 {
545   return mStyleChangedSignal;
546 }
547
548 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
549 {
550   return mTextModifiedSignal;
551 }
552
553 Toolkit::TextInput::MaxInputCharactersReachedSignalType& TextInput::MaxInputCharactersReachedSignal()
554 {
555   return mMaxInputCharactersReachedSignal;
556 }
557
558 Toolkit::TextInput::InputTextExceedBoundariesSignalType& TextInput::InputTextExceedBoundariesSignal()
559 {
560   return mInputTextExceedBoundariesSignal;
561 }
562
563 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
564 {
565   Dali::BaseHandle handle( object );
566
567   bool connected( true );
568   Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( handle );
569
570   if( 0 == strcmp( signalName.c_str(), SIGNAL_START_INPUT ) )
571   {
572     textInput.InputStartedSignal().Connect( tracker, functor );
573   }
574   else if( 0 == strcmp( signalName.c_str(), SIGNAL_END_INPUT ) )
575   {
576     textInput.InputFinishedSignal().Connect( tracker, functor );
577   }
578   else if( 0 == strcmp( signalName.c_str(), SIGNAL_STYLE_CHANGED ) )
579   {
580     textInput.StyleChangedSignal().Connect( tracker, functor );
581   }
582   else if( 0 == strcmp( signalName.c_str(), SIGNAL_MAX_INPUT_CHARACTERS_REACHED ) )
583   {
584     textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
585   }
586   else if( 0 == strcmp( signalName.c_str(), SIGNAL_TOOLBAR_DISPLAYED ) )
587   {
588     textInput.CutAndPasteToolBarDisplayedSignal().Connect( tracker, functor );
589   }
590   else if( 0 == strcmp( signalName.c_str(), SIGNAL_TEXT_EXCEED_BOUNDARIES ) )
591   {
592     textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
593   }
594   else
595   {
596     // signalName does not match any signal
597     connected = false;
598   }
599
600   return connected;
601 }
602
603 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
604 {
605   if(editMode)
606   {
607     // update line height before calculate the actual position.
608     UpdateLineHeight();
609
610     if(!mEditModeActive)
611     {
612       if( setCursorOnTouchPoint )
613       {
614         // Sets the cursor position for the given touch point.
615         ReturnClosestIndex( touchPoint, mCursorPosition );
616
617         // Creates the grab handle.
618         if( IsGrabHandleEnabled() )
619         {
620           const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
621
622           CreateGrabHandle();
623
624           mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
625           mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
626           mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
627           ShowGrabHandleAndSetVisibility( true );
628
629           // Scrolls the text-view if needed.
630           if( IsScrollEnabled() )
631           {
632             ScrollTextViewToMakeCursorVisible( cursorPosition );
633           }
634         }
635       }
636       else
637       {
638         mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
639       }
640     }
641
642     StartEditMode();
643   }
644   else
645   {
646     EndEditMode();
647   }
648 }
649
650 bool TextInput::IsEditable() const
651 {
652   return mEditModeActive;
653 }
654
655 void TextInput::SetEditOnTouch( bool editOnTouch )
656 {
657   mEditOnTouch = editOnTouch;
658 }
659
660 bool TextInput::IsEditOnTouch() const
661 {
662   return mEditOnTouch;
663 }
664
665 void TextInput::SetTextSelectable( bool textSelectable )
666 {
667   mTextSelection = textSelectable;
668 }
669
670 bool TextInput::IsTextSelectable() const
671 {
672   return mTextSelection;
673 }
674
675 bool TextInput::IsTextSelected() const
676 {
677   return mHighlightMeshActor;
678 }
679
680 void TextInput::DeSelectText()
681 {
682   RemoveHighlight();
683   HidePopup();
684   CursorUpdate();
685 }
686
687 void TextInput::SetGrabHandleImage(Dali::Image image )
688 {
689   if (image)
690   {
691     CreateGrabHandle(image);
692   }
693 }
694
695 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
696 {
697   DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
698
699   if ( image )
700   {
701     mCursor.SetImage( image );
702     mCursor.SetNinePatchBorder( border );
703   }
704 }
705
706 Vector3 TextInput::GetSelectionHandleSize()
707 {
708   return DEFAULT_SELECTION_HANDLE_SIZE;
709 }
710
711 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
712 {
713   DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
714
715   if ( image )
716   {
717     mCursorRTL.SetImage( image);
718     mCursorRTL.SetNinePatchBorder(  border );
719   }
720 }
721
722 void TextInput::EnableGrabHandle(bool toggle)
723 {
724   // enables grab handle with will in turn de-activate magnifier
725   mGrabHandleEnabled = toggle;
726 }
727
728 bool TextInput::IsGrabHandleEnabled()
729 {
730   // if false then magnifier will be shown instead.
731   return mGrabHandleEnabled;
732 }
733
734 void TextInput::EnableSelectionHandleFlip( bool toggle )
735 {
736   // Deprecated function.  To be removed.
737   mIsSelectionHandleFlipEnabled = toggle;
738 }
739
740 bool TextInput::IsSelectionHandleFlipEnabled()
741 {
742   // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
743   return true;
744 }
745
746 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
747 {
748   // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
749   Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
750   const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
751
752   mSelectionHandleFlipMargin = margin;
753 }
754
755 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
756 {
757   // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
758   Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
759
760   const float originX = boundingRectangle.x - 0.5f * stageSize.width;
761   const float originY = boundingRectangle.y - 0.5f * stageSize.height;
762
763   const Vector4 boundary( originX,
764                           originY,
765                           originX + boundingRectangle.width,
766                           originY + boundingRectangle.height );
767
768   mBoundingRectangleWorldCoordinates = boundary;
769 }
770
771 const Rect<float> TextInput::GetBoundingRectangle() const
772 {
773   Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
774
775   const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
776   const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
777
778   Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
779
780   return boundingRect;
781 }
782
783 const Vector4& TextInput::GetSelectionHandleFlipMargin()
784 {
785   return mSelectionHandleFlipMargin;
786 }
787
788 void TextInput::SetTextColor( const Vector4& color )
789 {
790   mDisplayedTextView.SetColor( color );
791 }
792
793 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
794 {
795   if( style != mInputStyle )
796   {
797     // different style.
798     bool emitSignal = false;
799
800     // mask: modify style according to mask, if different emit signal.
801     const TextStyle oldInputStyle( mInputStyle );
802
803     // Copy the new style.
804     mInputStyle.Copy( style, mask );
805
806     // if style has changed, emit signal.
807     if( oldInputStyle != mInputStyle )
808     {
809       emitSignal = true;
810     }
811
812     // Updates the line height accordingly with the input style.
813     UpdateLineHeight();
814
815     // Changing font point size will require the cursor to be re-sized
816     DrawCursor();
817
818     if( emitSignal )
819     {
820       EmitStyleChangedSignal();
821     }
822   }
823 }
824
825 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
826 {
827   if ( IsTextSelected() )
828   {
829     const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
830     const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
831
832     if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
833     {
834       ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
835     }
836
837     // Keeps the old style to be compared with the new one.
838     const TextStyle oldInputStyle( mInputStyle );
839
840     // Copy only those parameters from the style which are set in the mask.
841     mInputStyle.Copy( style, mask );
842
843     if( mInputStyle != oldInputStyle )
844     {
845       // Updates the line height accordingly with the input style.
846       UpdateLineHeight();
847
848       EmitStyleChangedSignal();
849     }
850   }
851 }
852
853 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
854 {
855   if( !mStyledText.empty() )
856   {
857     ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
858   }
859 }
860
861 TextStyle TextInput::GetStyleAtCursor() const
862 {
863   TextStyle style;
864
865   if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
866   {
867     DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
868     style = mStyledText.at( mCursorPosition-1 ).mStyle;
869   }
870   else // No text.
871   {
872     style = mInputStyle;
873
874     if ( mInputStyle.GetFontPointSize() <  Math::MACHINE_EPSILON_1000 )
875     {
876       Dali::Font defaultFont = Dali::Font::New();
877       style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
878     }
879   }
880
881   return style;
882 }
883
884 TextStyle TextInput::GetStyleAt( std::size_t position ) const
885 {
886   DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
887
888   if( position >= mStyledText.size() )
889   {
890     position = mStyledText.size() - 1;
891   }
892
893   return mStyledText.at( position ).mStyle;
894 }
895
896 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
897 {
898   mDisplayedTextView.SetTextAlignment( align );
899   mOverrideAutomaticAlignment = true;
900 }
901
902 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
903 {
904   mDisplayedTextView.SetLineJustification( justification );
905   mOverrideAutomaticAlignment = true;
906 }
907
908 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
909 {
910   mDisplayedTextView.SetFadeBoundary( fadeBoundary );
911 }
912
913 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
914 {
915   return mDisplayedTextView.GetFadeBoundary();
916 }
917
918 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
919 {
920   return mDisplayedTextView.GetTextAlignment();
921 }
922
923 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
924 {
925   mDisplayedTextView.SetMultilinePolicy( policy );
926 }
927
928 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
929 {
930   return mDisplayedTextView.GetMultilinePolicy();
931 }
932
933 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
934 {
935   mDisplayedTextView.SetWidthExceedPolicy( policy );
936 }
937
938 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
939 {
940   return mDisplayedTextView.GetWidthExceedPolicy();
941 }
942
943 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
944 {
945   mDisplayedTextView.SetHeightExceedPolicy( policy );
946 }
947
948 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
949 {
950   return mDisplayedTextView.GetHeightExceedPolicy();
951 }
952
953 void TextInput::SetExceedEnabled( bool enable )
954 {
955   mExceedEnabled = enable;
956 }
957
958 bool TextInput::GetExceedEnabled() const
959 {
960   return mExceedEnabled;
961 }
962
963 void TextInput::SetBackground(Dali::Image image )
964 {
965   // TODO Should add this function and add public api to match.
966 }
967
968 bool TextInput::OnTouchEvent(const TouchEvent& event)
969 {
970   return false;
971 }
972
973 bool TextInput::OnKeyEvent(const KeyEvent& event)
974 {
975   switch( event.state )
976   {
977     case KeyEvent::Down:
978     {
979       return OnKeyDownEvent(event);
980     }
981     break;
982
983     case KeyEvent::Up:
984     {
985       return OnKeyUpEvent(event);
986     }
987     break;
988
989     default:
990     {
991       return false;
992     }
993     break;
994   }
995 }
996
997 void TextInput::OnKeyInputFocusGained()
998 {
999   DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1000
1001   mEditModeActive = true;
1002
1003   mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1004
1005   mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1006
1007   // Updates the line height accordingly with the input style.
1008   UpdateLineHeight();
1009
1010   // Connect the signals to use in text input.
1011   VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1012   VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1013
1014   // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1015   SetTextDirection();
1016
1017   GetTextLayoutInfo();
1018
1019   DrawCursor();
1020   SetCursorVisibility( true );
1021   StartCursorBlinkTimer();
1022
1023   Toolkit::TextInput handle( GetOwner() );
1024   mInputStartedSignal.Emit( handle );
1025
1026   ImfManager imfManager = ImfManager::Get();
1027
1028   if ( imfManager )
1029   {
1030     imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1031
1032     // Notify that the text editing start.
1033     imfManager.Activate();
1034
1035     // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1036     imfManager.SetRestoreAfterFocusLost( true );
1037
1038     imfManager.SetCursorPosition( mCursorPosition );
1039     imfManager.NotifyCursorPosition();
1040   }
1041
1042   mClipboard = Clipboard::Get(); // Store handle to clipboard
1043
1044   // Now in edit mode we can accept string to paste from clipboard
1045   ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1046   if ( notifier )
1047   {
1048     notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1049   }
1050 }
1051
1052 void TextInput::OnKeyInputFocusLost()
1053 {
1054   DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1055
1056   if( mPreEditFlag )
1057   {
1058     // If key input focus is lost, it removes the
1059     // underline from the last pre-edit text.
1060     RemovePreEditStyle();
1061     const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1062     InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1063     EmitTextModified();
1064   }
1065
1066   ImfManager imfManager = ImfManager::Get();
1067   if ( imfManager )
1068   {
1069     // The text editing is finished. Therefore the imf manager don't have restore activation.
1070     imfManager.SetRestoreAfterFocusLost( false );
1071
1072     // Notify that the text editing finish.
1073     imfManager.Deactivate();
1074
1075     imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1076   }
1077   // Disconnect signal used the text input.
1078   VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1079
1080   Toolkit::TextInput handle( GetOwner() );
1081   mInputFinishedSignal.Emit( handle );
1082   mEditModeActive = false;
1083   mPreEditFlag = false;
1084   RemoveHighlight();
1085   SetCursorVisibility( false );
1086   StopCursorBlinkTimer();
1087
1088   ShowGrabHandleAndSetVisibility( false );
1089
1090   mClipboard.Reset();
1091   // No longer in edit mode so do not want to receive string from clipboard
1092   ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1093   if ( notifier )
1094   {
1095     notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1096   }
1097
1098   Clipboard clipboard = Clipboard::Get();
1099   if ( clipboard )
1100   {
1101     clipboard.HideClipboard();
1102   }
1103 }
1104
1105 void TextInput::OnControlStageConnection()
1106 {
1107   Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1108
1109   if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1110   {
1111     SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1112   }
1113 }
1114
1115 void TextInput::CreateActiveLayer()
1116 {
1117   Actor self = Self();
1118   mActiveLayer = Layer::New();
1119   mActiveLayer.SetName ( "ActiveLayerActor" );
1120
1121   mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1122   mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1123   mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1124
1125   self.Add( mActiveLayer );
1126   mActiveLayer.RaiseToTop();
1127 }
1128
1129 void TextInput::OnInitialize()
1130 {
1131   CreateTextViewActor();
1132
1133   SetUpTouchEvents();
1134
1135   // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1136   // different positions depending on language)
1137   mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1138   mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1139
1140   Actor self = Self();
1141   self.Add( mCursor );
1142   self.Add( mCursorRTL );
1143
1144   mCursorVisibility = false;
1145
1146   CreateActiveLayer(); // todo move this so layer only created when needed.
1147
1148   // Assign names to image actors
1149   mCursor.SetName("mainCursor");
1150   mCursorRTL.SetName("rtlCursor");
1151 }
1152
1153 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1154 {
1155   mDisplayedTextView.SetSize( targetSize );
1156   GetTextLayoutInfo();
1157   mActiveLayer.SetSize(targetSize);
1158 }
1159
1160 void TextInput::OnRelayout( const Vector2& size, RelayoutContainer& container )
1161 {
1162   container.Add( mDisplayedTextView, size );
1163   container.Add( mPopupPanel.GetRootActor(), size );
1164
1165   GetTextLayoutInfo();
1166
1167   DrawCursor();
1168 }
1169
1170 Vector3 TextInput::GetNaturalSize()
1171 {
1172   Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1173
1174   if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1175   {
1176     // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1177     naturalSize.height = mLineHeight;
1178   }
1179
1180   return naturalSize;
1181 }
1182
1183 float TextInput::GetHeightForWidth( float width )
1184 {
1185   float height = mDisplayedTextView.GetHeightForWidth( width );
1186
1187   if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1188   {
1189     // If the height is zero, it means there is no text. Let's return the cursor height.
1190     height = mLineHeight;
1191   }
1192
1193   return height;
1194 }
1195
1196 /*end of Virtual methods from parent*/
1197
1198 // Private Internal methods
1199
1200 void TextInput::OnHandlePan(Actor actor, const PanGesture& gesture)
1201 {
1202   switch (gesture.state)
1203   {
1204     case Gesture::Started:
1205     // fall through so code not duplicated
1206     case Gesture::Continuing:
1207     {
1208       if (actor == mGrabArea)
1209       {
1210         SetCursorVisibility( true );
1211         ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1212         MoveGrabHandle( gesture.displacement );
1213         HidePopup(); // Do not show popup whilst handle is moving
1214       }
1215       else if (actor == mHandleOneGrabArea)
1216       {
1217         // the displacement in PanGesture is affected by the actor's rotation.
1218         mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1219         mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1220
1221         MoveSelectionHandle( HandleOne, gesture.displacement );
1222
1223         mState = StateDraggingHandle;
1224         HidePopup();
1225       }
1226       else if (actor == mHandleTwoGrabArea)
1227       {
1228         // the displacement in PanGesture is affected by the actor's rotation.
1229         mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1230         mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1231
1232         MoveSelectionHandle( HandleTwo, gesture.displacement );
1233
1234         mState = StateDraggingHandle;
1235         HidePopup();
1236       }
1237     }
1238     break;
1239
1240     case Gesture::Finished:
1241     {
1242       // Revert back to non-pressed selection handle images
1243       if (actor == mGrabArea)
1244       {
1245         mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1246         SetCursorVisibility( true );
1247         SetUpPopupSelection();
1248         ShowPopup();
1249       }
1250       if (actor == mHandleOneGrabArea)
1251       {
1252         // the displacement in PanGesture is affected by the actor's rotation.
1253         mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1254         mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1255
1256         mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1257
1258         mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1259         mState = StateEdit;
1260         ShowPopupCutCopyPaste();
1261       }
1262       if (actor == mHandleTwoGrabArea)
1263       {
1264         // the displacement in PanGesture is affected by the actor's rotation.
1265         mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1266         mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1267
1268         mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1269
1270         mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1271         mState = StateEdit;
1272         ShowPopupCutCopyPaste();
1273       }
1274     }
1275     break;
1276     default:
1277       break;
1278   }
1279 }
1280
1281 // Stop the flashing animation so easy to see when moved.
1282 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1283 {
1284   if (touch.GetPoint(0).state == TouchPoint::Down)
1285   {
1286     SetCursorVisibility( true );
1287     StopCursorBlinkTimer();
1288   }
1289   else if (touch.GetPoint(0).state == TouchPoint::Up)
1290   {
1291     SetCursorVisibility( true );
1292     StartCursorBlinkTimer();
1293   }
1294   return false;
1295 }
1296
1297 // selection handle one
1298 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1299 {
1300   if (touch.GetPoint(0).state == TouchPoint::Down)
1301   {
1302     mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1303   }
1304   else if (touch.GetPoint(0).state == TouchPoint::Up)
1305   {
1306     mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1307   }
1308   return false;
1309 }
1310
1311 // selection handle two
1312 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1313 {
1314   if (touch.GetPoint(0).state == TouchPoint::Down)
1315   {
1316     mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1317   }
1318   else if (touch.GetPoint(0).state == TouchPoint::Up)
1319   {
1320     mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1321   }
1322   return false;
1323 }
1324
1325 void TextInput::OnDoubleTap(Dali::Actor actor, const Dali::TapGesture& tap)
1326 {
1327    // If text exists then select nearest word.
1328    if ( !mStyledText.empty())
1329    {
1330      HidePopup();
1331
1332      ShowGrabHandleAndSetVisibility( false );
1333
1334
1335      if ( mPreEditFlag )
1336      {
1337        // PreEdit will be committed here without needing a commit from IMF.  Remove pre-edit underline and reset flags which
1338        // converts the pre-edit word being displayed to a committed word.
1339        if ( !mUnderlinedPriorToPreEdit )
1340        {
1341          TextStyle style;
1342          style.SetUnderline( false );
1343          ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1344        }
1345        mPreEditFlag = false;
1346        mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1347        // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1348        PreEditReset( false );
1349      }
1350      mCursorPosition = 0;
1351
1352      mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1353      ReturnClosestIndex( tap.localPoint, mCursorPosition );
1354
1355      std::size_t start = 0;
1356      std::size_t end = 0;
1357      Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1358
1359      mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1360
1361      ImfManager imfManager = ImfManager::Get();
1362      if ( imfManager )
1363      {
1364        imfManager.SetCursorPosition ( mCursorPosition );
1365        imfManager.NotifyCursorPosition();
1366      }
1367
1368      if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1369      {
1370        SelectText( start, end );
1371        ShowPopupCutCopyPaste();
1372      }
1373      else
1374      {
1375        RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1376        HidePopup( false ); // Hide popup with setting to do auto show.
1377        SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1378        ShowPopup();
1379      }
1380    }
1381    else if ( mClipboard && mClipboard.NumberOfItems() )
1382    {
1383      ShowPopupCutCopyPaste();
1384    }
1385
1386    // If no text and clipboard empty then do nothing
1387 }
1388
1389 // TODO: Change the function name to be more general.
1390 void TextInput::OnTextTap(Dali::Actor actor, const Dali::TapGesture& tap)
1391 {
1392   DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1393                                                                                                            , (mEditOnTouch)?"true":"false"
1394                                                                                                            , (mEditModeActive)?"true":"false");
1395
1396   if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1397   {
1398     return;
1399   }
1400
1401   if( mGrabArea == actor )
1402   {
1403     if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1404     {
1405       SetUpPopupSelection();
1406       ShowPopup();
1407     }
1408
1409     return;
1410   }
1411
1412   HidePopup();
1413   RemoveHighlight();
1414
1415   mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1416
1417   // Initially don't create the grab handle.
1418   bool createGrabHandle = false;
1419
1420   if ( !mEditModeActive )
1421   {
1422     // update line height before calculate the actual position.
1423     UpdateLineHeight();
1424
1425     // Only start edit mode if TextInput configured to edit on touch
1426     if ( mEditOnTouch )
1427     {
1428       // Set the initial cursor position in the tap point.
1429       ReturnClosestIndex(tap.localPoint, mCursorPosition );
1430       StartEditMode();
1431     }
1432   }
1433   else
1434   {
1435     // Show the keyboard if it was hidden.
1436     if (!VirtualKeyboard::IsVisible())
1437     {
1438       VirtualKeyboard::Show();
1439     }
1440
1441     // Reset keyboard as tap event has occurred.
1442     // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1443     PreEditReset( true );
1444
1445     GetTextLayoutInfo();
1446
1447     if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1448     {
1449       // 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.
1450
1451       ReturnClosestIndex(tap.localPoint, mCursorPosition );
1452
1453       DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1454
1455       // Notify keyboard so it can 're-capture' word for predictive text.
1456       // As we have done a reset, is this required, expect IMF keyboard to request this information.
1457       ImfManager imfManager = ImfManager::Get();
1458       if ( imfManager )
1459       {
1460         imfManager.SetCursorPosition ( mCursorPosition );
1461         imfManager.NotifyCursorPosition();
1462       }
1463       const TextStyle oldInputStyle( mInputStyle );
1464
1465       mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1466
1467       DrawCursor();
1468
1469       // Create the grab handle.
1470       // Grab handle is created later.
1471       createGrabHandle = true;
1472
1473       if( oldInputStyle != mInputStyle )
1474       {
1475         // Updates the line height accordingly with the input style.
1476         UpdateLineHeight();
1477
1478         EmitStyleChangedSignal();
1479       }
1480     }
1481   }
1482
1483   // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1484   // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1485   // otherwise the Grab handle will be shown when selecting.
1486   if ( createGrabHandle && IsGrabHandleEnabled() )
1487   {
1488     Vector3 altPosition;    // Alternate (i.e. opposite direction) cursor position.
1489     bool altPositionValid;  // Alternate cursor validity flag.
1490     bool directionRTL;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1491     Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1492
1493     if( altPositionValid )
1494     {
1495       // Check which of the positions is the closest.
1496       if( fabsf( altPosition.x - tap.localPoint.x ) < fabsf( cursorPosition.x - tap.localPoint.x ) )
1497       {
1498         cursorPosition = altPosition;
1499       }
1500     }
1501
1502     CreateGrabHandle();
1503
1504     mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1505     mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1506     mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1507     ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1508
1509   }
1510 }
1511
1512 void TextInput::OnLongPress(Dali::Actor actor, const Dali::LongPressGesture& longPress)
1513 {
1514   DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1515
1516   // Ignore longpress if in selection mode already
1517   if( mHighlightMeshActor )
1518   {
1519     return;
1520   }
1521
1522   if(longPress.state == Dali::Gesture::Started)
1523   {
1524     // Start edit mode on long press
1525     if ( !mEditModeActive )
1526     {
1527       StartEditMode();
1528     }
1529
1530     // If text exists then select nearest word.
1531     if ( !mStyledText.empty())
1532     {
1533       HidePopup();
1534
1535       ShowGrabHandleAndSetVisibility( false );
1536
1537
1538       if ( mPreEditFlag )
1539       {
1540         // PreEdit will be committed here without needing a commit from IMF.  Remove pre-edit underline and reset flags which
1541         // converts the pre-edit word being displayed to a committed word.
1542         if ( !mUnderlinedPriorToPreEdit )
1543         {
1544           TextStyle style;
1545           style.SetUnderline( false );
1546           ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1547         }
1548         mPreEditFlag = false;
1549         mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1550         // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1551         PreEditReset( false );
1552       }
1553       mCursorPosition = 0;
1554
1555       mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1556       ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1557
1558       std::size_t start = 0;
1559       std::size_t end = 0;
1560       Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1561
1562       mCursorPosition  = end; // Ensure cursor is positioned at end of selected word
1563
1564       ImfManager imfManager = ImfManager::Get();
1565       if ( imfManager )
1566       {
1567         imfManager.SetCursorPosition ( mCursorPosition );
1568         imfManager.NotifyCursorPosition();
1569       }
1570
1571       SelectText( start, end );
1572     }
1573
1574     // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1575     if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1576     {
1577       ShowPopupCutCopyPaste();
1578     }
1579   }
1580 }
1581
1582 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1583 {
1584   const Text clipboardText( notifier.GetContent() );
1585   PasteText( clipboardText );
1586
1587   SetCursorVisibility( true );
1588   StartCursorBlinkTimer();
1589
1590   ShowGrabHandleAndSetVisibility( false );
1591
1592
1593   HidePopup();
1594 }
1595
1596 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1597 {
1598   mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1599
1600   const std::string& name = button.GetName();
1601
1602   if(name == TextInputPopup::OPTION_SELECT_WORD)
1603   {
1604     std::size_t start = 0;
1605     std::size_t end = 0;
1606     Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1607
1608     SelectText( start, end );
1609   }
1610   else if(name == TextInputPopup::OPTION_SELECT_ALL)
1611   {
1612     SetCursorVisibility(false);
1613     StopCursorBlinkTimer();
1614
1615     std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1616     std::size_t start = 0;
1617
1618     SelectText( start, end );
1619   }
1620   else if(name == TextInputPopup::OPTION_CUT)
1621   {
1622     bool ret = CopySelectedTextToClipboard();
1623
1624     if ( ret )
1625     {
1626       DeleteHighlightedText( true );
1627       CursorUpdate();
1628     }
1629
1630     SetCursorVisibility( true );
1631     StartCursorBlinkTimer();
1632
1633     HidePopup();
1634   }
1635   else if(name == TextInputPopup::OPTION_COPY)
1636   {
1637     CopySelectedTextToClipboard();
1638
1639     RemoveHighlight();
1640
1641     SetCursorVisibility( true );
1642     StartCursorBlinkTimer();
1643
1644     HidePopup();
1645   }
1646   else if(name == TextInputPopup::OPTION_PASTE)
1647   {
1648     const Text retrievedString( mClipboard.GetItem( 0 ) );  // currently can only get first item in clip board, index 0;
1649
1650     PasteText(retrievedString);
1651
1652     SetCursorVisibility( true );
1653     StartCursorBlinkTimer();
1654
1655     ShowGrabHandleAndSetVisibility( false );
1656
1657     HidePopup();
1658   }
1659   else if(name == TextInputPopup::OPTION_CLIPBOARD)
1660   {
1661     // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1662     // Hence pass the false parameter for signalFinished.
1663     HidePopup( true, false );
1664     mClipboard.ShowClipboard();
1665   }
1666
1667   return false;
1668 }
1669
1670 bool TextInput::OnCursorBlinkTimerTick()
1671 {
1672   // Cursor blinking
1673   mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1674   if ( mCursorRTLEnabled )
1675   {
1676     mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1677   }
1678   mCursorBlinkStatus = !mCursorBlinkStatus;
1679
1680   return true;
1681 }
1682
1683 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1684 {
1685   popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1686
1687   // Change Popup menu to Cut/Copy/Paste if text has been selected.
1688   if(mHighlightMeshActor && mState == StateEdit)
1689   {
1690     ShowPopupCutCopyPaste();
1691   }
1692 }
1693
1694 //FIXME this routine needs to be re-written as it contains too many branches.
1695 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1696 {
1697   std::string keyName = event.keyPressedName;
1698   std::string keyString = event.keyPressed;
1699
1700   DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1701
1702   // Do not consume "Tab" and "Escape" keys.
1703   if(keyName == "Tab" || keyName == "Escape")
1704   {
1705     // Escape key to end the edit mode
1706     EndEditMode();
1707
1708     return false;
1709   }
1710
1711   HidePopup(); // If Pop-up shown then hides it as editing text.
1712
1713   // Update Flag, indicates whether to update the text-input contents or not.
1714   // Any key stroke that results in a visual change of the text-input should
1715   // set this flag to true.
1716   bool update(false);
1717
1718   // Whether to scroll text to cursor position.
1719   // Scroll is needed always the cursor is updated and after the pre-edit is received.
1720   bool scroll = false;
1721
1722   if (keyName == "Return")
1723   {
1724     if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1725     {
1726       bool preEditFlagPreviouslySet( mPreEditFlag );
1727
1728       // replaces highlighted text with new line
1729       DeleteHighlightedText( false );
1730
1731       mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1732
1733       // If we are in pre-edit mode then pressing enter will cause a commit.  But the commit string does not include the
1734       // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1735       if ( mPreEditFlag )
1736       {
1737         mCommitByKeyInput = true;
1738       }
1739
1740       // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1741       if ( preEditFlagPreviouslySet && !mPreEditFlag )
1742       {
1743         mPreEditFlag = true;
1744         mIgnoreCommitFlag = false;
1745       }
1746       EmitTextModified();
1747       update = true;
1748     }
1749     else
1750     {
1751       RemoveHighlight();
1752     }
1753   } // Return
1754   else if ( keyName == "space" )
1755   {
1756     if ( mHighlightMeshActor )
1757     {
1758       // Some text is selected so erase it before adding space.
1759       DeleteHighlightedText( true );
1760     }
1761
1762     mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1763
1764     // If we are in pre-edit mode then pressing the space-bar will cause a commit.  But the commit string does not include the
1765     // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1766     if ( mPreEditFlag )
1767     {
1768       mCommitByKeyInput = true;
1769     }
1770     EmitTextModified();
1771     update = true;
1772   } // space
1773   else if (keyName == "BackSpace")
1774   {
1775     if ( mHighlightMeshActor )
1776     {
1777       // Some text is selected so erase it
1778       DeleteHighlightedText( true );
1779       update = true;
1780     }
1781     else
1782     {
1783       if ( mCursorPosition > 0 )
1784       {
1785         DeleteCharacter( mCursorPosition );
1786         update = true;
1787       }
1788     }
1789     EmitTextModified();
1790   } // BackSpace
1791   else if (keyName == "Right")
1792   {
1793     AdvanceCursor();
1794     RemoveHighlight();
1795   }
1796   else if (keyName == "Left")
1797   {
1798     AdvanceCursor(true);
1799     RemoveHighlight();
1800   }
1801   else // event is a character
1802   {
1803     // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1804     if ( !keyString.empty() )
1805     {
1806       // replaces highlighted text with new character
1807       DeleteHighlightedText( false );
1808
1809       // Received key String
1810       mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1811       update = true;
1812       EmitTextModified();
1813     }
1814   }
1815
1816   // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1817   // as this is a costly operation.
1818   if(update)
1819   {
1820     CursorUpdate();
1821   }
1822
1823   if(update || scroll)
1824   {
1825     if( IsScrollEnabled() )
1826     {
1827       // Calculates the new cursor position (in actor coordinates)
1828       const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1829
1830       ScrollTextViewToMakeCursorVisible( cursorPosition );
1831     }
1832   }
1833
1834   return true;
1835 }
1836
1837 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1838 {
1839   std::string keyName = event.keyPressedName;
1840   std::string keyString = event.keyPressed;
1841
1842   DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1843
1844   // The selected text become deselected when the key code is DALI_KEY_BACK.
1845   if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1846   {
1847     DeSelectText();
1848     return true;
1849   }
1850
1851   return false;
1852 }
1853
1854 void TextInput::ChooseRtlSelectionHandlePosition( const Vector3& cursorPositionOne,
1855                                                   const Vector3& cursorPositionTwo,
1856                                                   bool altPositionValidOne,
1857                                                   bool altPositionValidTwo,
1858                                                   const Vector3& altPositionOne,
1859                                                   const Vector3& altPositionTwo )
1860 {
1861   // TODO VCC Valid for one line.
1862   // Try to place the selection handles. TODO think in something better. Probably need to know the direction of the paragraph.
1863   if( cursorPositionOne != cursorPositionTwo )
1864   {
1865     if( cursorPositionOne.x < cursorPositionTwo.x )
1866     {
1867       mSelectionHandleOneActualPosition = cursorPositionOne;
1868       mSelectionHandleTwoActualPosition = cursorPositionTwo;
1869     }
1870     else
1871     {
1872       mSelectionHandleOneActualPosition = cursorPositionTwo;
1873       mSelectionHandleTwoActualPosition = cursorPositionOne;
1874     }
1875   }
1876   else
1877   {
1878     mSelectionHandleOneActualPosition = cursorPositionOne;
1879     if( altPositionValidOne )
1880     {
1881       if( altPositionOne.x < mSelectionHandleOneActualPosition.x )
1882       {
1883         mSelectionHandleOneActualPosition = altPositionOne;
1884       }
1885     }
1886     if( altPositionValidTwo )
1887     {
1888       if(  altPositionTwo.x < mSelectionHandleOneActualPosition.x )
1889       {
1890         mSelectionHandleOneActualPosition = altPositionTwo;
1891       }
1892     }
1893
1894     mSelectionHandleTwoActualPosition = cursorPositionTwo;
1895     if( altPositionValidTwo )
1896     {
1897       if(  altPositionTwo.x > mSelectionHandleTwoActualPosition.x )
1898       {
1899         mSelectionHandleTwoActualPosition = altPositionTwo;
1900       }
1901     }
1902     if( altPositionValidOne )
1903     {
1904       if( altPositionOne.x > mSelectionHandleTwoActualPosition.x )
1905       {
1906         mSelectionHandleTwoActualPosition = altPositionOne;
1907       }
1908     }
1909   }
1910 }
1911
1912 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1913 {
1914   // Updates the stored scroll position.
1915   mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1916
1917   const Vector3& controlSize = GetControlSize();
1918   Size cursorSize( CURSOR_THICKNESS, 0.f );
1919
1920   // Updates the cursor and grab handle position and visibility.
1921   if( mGrabHandle || mCursor )
1922   {
1923     cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1924
1925     Vector3 altPosition;    // Alternate (i.e. opposite direction) cursor position.
1926     bool altPositionValid;  // Alternate cursor validity flag.
1927     bool directionRTL;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1928     Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1929
1930     if( altPositionValid )
1931     {
1932       // Check which of the positions is the closest.
1933       if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( cursorPosition.x - mActualGrabHandlePosition.x ) )
1934       {
1935         cursorPosition = altPosition;
1936       }
1937     }
1938
1939     mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1940
1941     mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1942
1943     if( mGrabHandle )
1944     {
1945       ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1946       mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1947     }
1948
1949     if( mCursor )
1950     {
1951       mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1952       mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1953     }
1954   }
1955
1956   // Updates the selection handles and highlighted text position and visibility.
1957   if( mSelectionHandleOne && mSelectionHandleTwo )
1958   {
1959     Vector3 altPositionOne;    // Alternate (i.e. opposite direction) cursor position.
1960     bool altPositionValidOne;  // Alternate cursor validity flag.
1961     bool directionRTLOne;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1962     Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
1963
1964     Vector3 altPositionTwo;    // Alternate (i.e. opposite direction) cursor position.
1965     bool altPositionValidTwo;  // Alternate cursor validity flag.
1966     bool directionRTLTwo;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1967     Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
1968
1969     // VCC TODO: This method is a hack for one line.
1970     ChooseRtlSelectionHandlePosition( cursorPositionOne,
1971                                       cursorPositionTwo,
1972                                       altPositionValidOne,
1973                                       altPositionValidTwo,
1974                                       altPositionOne,
1975                                       altPositionTwo );
1976
1977     cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1978     const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1979     cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1980     const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1981
1982     mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1983     mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1984     mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1985     mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1986
1987     if( mHighlightMeshActor )
1988     {
1989       mHighlightMeshActor.SetVisible( true );
1990       UpdateHighlight();
1991     }
1992   }
1993 }
1994
1995 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1996 {
1997   // Scroll the text to make the cursor visible.
1998   const Size cursorSize( CURSOR_THICKNESS,
1999                          GetRowRectFromCharacterPosition( mCursorPosition ).height );
2000
2001   // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
2002
2003   const Vector3& controlSize = GetControlSize();
2004
2005   // Calculates the new scroll position.
2006   Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
2007   if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
2008   {
2009     scrollOffset.x += cursorPosition.x;
2010   }
2011
2012   if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
2013   {
2014     scrollOffset.y += cursorPosition.y;
2015   }
2016
2017   // Sets the new scroll position.
2018   SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
2019   SetScrollPosition( scrollOffset );
2020 }
2021
2022 void TextInput::StartScrollTimer()
2023 {
2024   if( !mScrollTimer )
2025   {
2026     mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
2027     mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
2028   }
2029
2030   if( !mScrollTimer.IsRunning() )
2031   {
2032     mScrollTimer.Start();
2033   }
2034 }
2035
2036 void TextInput::StopScrollTimer()
2037 {
2038   if( mScrollTimer )
2039   {
2040     mScrollTimer.Stop();
2041   }
2042 }
2043
2044 bool TextInput::OnScrollTimerTick()
2045 {
2046   // TODO: need to set the new style accordingly the new handle position.
2047
2048   if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
2049   {
2050     // nothing to do if all handles are invisible or doesn't exist.
2051     return true;
2052   }
2053
2054   // Text scrolling
2055
2056   // Choose between the grab handle or the selection handles.
2057   Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
2058   std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
2059   Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
2060
2061   std::size_t newCursorPosition = 0;
2062   ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2063
2064   // Whether the handle's position is different of the previous one and in the case of the selection handle,
2065   // the new selection handle's position needs to be different of the other one.
2066   const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2067                                          ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2068                                                                                 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2069
2070   if( differentSelectionHandles )
2071   {
2072     handlePosition = newCursorPosition;
2073
2074     const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2075
2076     Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2077
2078     Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2079     scrollPosition += scrollDelta;
2080     SetScrollPosition( scrollPosition );
2081
2082     if( mDisplayedTextView.IsScrollPositionTrimmed() )
2083     {
2084       StopScrollTimer();
2085     }
2086
2087     currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2088   }
2089
2090   actualHandlePosition.x += mScrollDisplacement.x;
2091   actualHandlePosition.y += mScrollDisplacement.y;
2092
2093   return true;
2094 }
2095
2096 // Public Internal Methods (public for testing purpose)
2097
2098 void TextInput::SetUpTouchEvents()
2099 {
2100   if ( !mTapDetector )
2101   {
2102     mTapDetector = TapGestureDetector::New();
2103     // Attach the actors and connect the signal
2104     mTapDetector.Attach(Self());
2105
2106     // As contains children which may register for tap the default control detector is not used.
2107     mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2108   }
2109
2110   if ( !mDoubleTapDetector )
2111   {
2112     mDoubleTapDetector = TapGestureDetector::New( 2 );
2113     mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2114
2115     // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2116     // so that we do not, unnecessarily, have a double tap request all the time
2117   }
2118
2119   if ( !mPanGestureDetector )
2120   {
2121     mPanGestureDetector = PanGestureDetector::New();
2122     mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2123   }
2124
2125   if ( !mLongPressDetector )
2126   {
2127     mLongPressDetector = LongPressGestureDetector::New();
2128     mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2129     mLongPressDetector.Attach(Self());
2130   }
2131 }
2132
2133 void TextInput::CreateTextViewActor()
2134 {
2135   mDisplayedTextView = Toolkit::TextView::New();
2136   mDisplayedTextView.SetName( "DisplayedTextView ");
2137   mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2138   mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2139   mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2140   mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2141   mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2142   mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2143   mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2144   mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2145   mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2146
2147   mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2148
2149   Self().Add( mDisplayedTextView );
2150 }
2151
2152 // Start a timer to initiate, used by the cursor to blink.
2153 void TextInput::StartCursorBlinkTimer()
2154 {
2155   if ( !mCursorBlinkTimer )
2156   {
2157     mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2158     mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2159   }
2160
2161   if ( !mCursorBlinkTimer.IsRunning() )
2162   {
2163     mCursorBlinkTimer.Start();
2164   }
2165 }
2166
2167 // Start a timer to initiate, used by the cursor to blink.
2168 void TextInput::StopCursorBlinkTimer()
2169 {
2170   if ( mCursorBlinkTimer )
2171   {
2172     mCursorBlinkTimer.Stop();
2173   }
2174 }
2175
2176 void TextInput::StartEditMode()
2177 {
2178   DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2179
2180   if(!mEditModeActive)
2181   {
2182     SetKeyInputFocus();
2183   }
2184
2185   if ( mDoubleTapDetector )
2186   {
2187     mDoubleTapDetector.Attach( Self() );
2188   }
2189 }
2190
2191 void TextInput::EndEditMode()
2192 {
2193   DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2194
2195   ClearKeyInputFocus();
2196
2197   if ( mDoubleTapDetector )
2198   {
2199     mDoubleTapDetector.Detach( Self() );
2200   }
2201 }
2202
2203 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2204 {
2205   if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2206   {
2207     mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2208     TextStyle style;
2209     style.SetUnderline( true );
2210     ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2211   }
2212 }
2213
2214 void TextInput::RemovePreEditStyle()
2215 {
2216   if ( !mUnderlinedPriorToPreEdit )
2217   {
2218     TextStyle style;
2219     style.SetUnderline( false );
2220     SetActiveStyle( style, TextStyle::UNDERLINE );
2221   }
2222 }
2223
2224 // IMF related methods
2225
2226
2227 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData&  imfEvent )
2228 {
2229   bool update( false );
2230   bool preeditResetRequired ( false );
2231
2232   if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2233   {
2234     HidePopup(); // If Pop-up shown then hides it as editing text.
2235   }
2236
2237   switch ( imfEvent.eventName )
2238   {
2239     case ImfManager::PREEDIT:
2240     {
2241       mIgnoreFirstCommitFlag = false;
2242
2243       // 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
2244       if ( mHighlightMeshActor &&  (!imfEvent.predictiveString.empty()) )
2245       {
2246         // replaces highlighted text with new character
2247         DeleteHighlightedText( false );
2248       }
2249
2250       preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2251
2252       if( IsScrollEnabled() )
2253       {
2254         // Calculates the new cursor position (in actor coordinates)
2255         const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2256         ScrollTextViewToMakeCursorVisible( cursorPosition );
2257       }
2258
2259       update = true;
2260
2261       break;
2262     }
2263     case ImfManager::COMMIT:
2264     {
2265       if( mIgnoreFirstCommitFlag )
2266       {
2267         // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2268         mIgnoreFirstCommitFlag = false;
2269       }
2270       else
2271       {
2272         // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2273
2274         // 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
2275         if ( mHighlightMeshActor &&  (!imfEvent.predictiveString.empty()) )
2276         {
2277           // replaces highlighted text with new character
2278           DeleteHighlightedText( false );
2279         }
2280
2281        // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2282        // not needed, one such scenario is when the pre-edit word is too long to fit.
2283        if ( !mIgnoreCommitFlag )
2284        {
2285          update = CommitReceived( imfEvent.predictiveString );
2286        }
2287        else
2288        {
2289          mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2290        }
2291       }
2292
2293       if( update )
2294       {
2295         if( IsScrollEnabled() )
2296         {
2297           // Calculates the new cursor position (in actor coordinates)
2298           const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2299
2300           ScrollTextViewToMakeCursorVisible( cursorPosition );
2301         }
2302       }
2303       break;
2304     }
2305     case ImfManager::DELETESURROUNDING:
2306     {
2307       DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2308                      (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2309
2310       mPreEditFlag = false;
2311
2312       std::size_t toDelete = 0;
2313       std::size_t numberOfCharacters = 0;
2314
2315       if( mHighlightMeshActor )
2316       {
2317         // delete highlighted text.
2318         toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2319         numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2320       }
2321       else
2322       {
2323         if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2324         {
2325           toDelete = mCursorPosition + imfEvent.cursorOffset;
2326         }
2327         if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2328         {
2329           numberOfCharacters = mStyledText.size() - toDelete;
2330         }
2331         else
2332         {
2333           numberOfCharacters = imfEvent.numberOfChars;
2334         }
2335       }
2336       DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2337       DeleteRange( toDelete, numberOfCharacters );
2338
2339       mCursorPosition = toDelete;
2340       mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2341
2342       EmitTextModified();
2343
2344       DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2345       break;
2346     }
2347     case ImfManager::GETSURROUNDING:
2348     {
2349       // 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
2350       // the next key pressed.  Instead the Select function sets the cursor position and surrounding text.
2351       if (! ( mHighlightMeshActor || mSelectingText ) )
2352       {
2353         std::string text( GetText() );
2354         DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2355
2356         imfManager.SetCursorPosition( mCursorPosition );
2357         imfManager.SetSurroundingText( text );
2358       }
2359
2360       if( 0 != mNumberOfSurroundingCharactersDeleted )
2361       {
2362         mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2363         mNumberOfSurroundingCharactersDeleted = 0;
2364
2365         if( mStyledText.empty() )
2366         {
2367           ShowPlaceholderText( mStyledPlaceHolderText );
2368         }
2369       }
2370       break;
2371     }
2372     case ImfManager::VOID:
2373     {
2374       DALI_ASSERT_DEBUG( false );
2375     }
2376   } // end switch
2377
2378   ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2379
2380   return callbackData;
2381 }
2382
2383 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2384 {
2385   mPreserveCursorPosition = false;  // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2386
2387   DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2388                 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2389
2390   bool preeditResetRequest ( false );
2391
2392   if( mPreEditFlag ) // Already in pre-edit state.
2393   {
2394     if( mStyledText.size() >= mMaxStringLength )
2395     {
2396       DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2397       // Cannot fit these characters into field, clear pre-edit.
2398       if ( !mUnderlinedPriorToPreEdit )
2399       {
2400         TextStyle style;
2401         style.SetUnderline( false );
2402         ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2403       }
2404       mIgnoreCommitFlag = true;
2405       preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2406       mPreEditFlag = false;
2407       EmitMaxInputCharactersReachedSignal();
2408     }
2409     else
2410     {
2411       // delete existing pre-edit string
2412       const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2413
2414       // Store new pre-edit string
2415       mPreEditString.SetText( keyString );
2416
2417       if ( keyString.empty() )
2418       {
2419         mPreEditFlag = false;
2420         mCursorPosition = mPreEditStartPosition;
2421
2422         if( mStyledText.empty() )
2423         {
2424           ShowPlaceholderText( mStyledPlaceHolderText );
2425         }
2426         else
2427         {
2428           mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2429         }
2430
2431         GetTextLayoutInfo();
2432         EmitTextModified();
2433       }
2434       else
2435       {
2436         // Insert new pre-edit string. InsertAt updates the size and position table.
2437         mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2438         // 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.
2439         mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2440         ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2441         DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2442         EmitTextModified();
2443       }
2444       // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2445       DrawCursor();
2446     }
2447   }
2448   else  // mPreEditFlag not set
2449   {
2450     if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2451     {
2452       DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2453       // new pre-edit so move into pre-edit state by setting flag
2454       mPreEditFlag = true;
2455       mPreEditString.SetText( keyString ); // store new pre-edit string
2456       mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2457       mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2458       // 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.
2459       mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2460       ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2461       DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2462       // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2463       DrawCursor();
2464       EmitTextModified();
2465     }
2466     else
2467     {
2468       DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2469     }
2470   }
2471
2472   return preeditResetRequest;
2473 }
2474
2475 bool TextInput::CommitReceived(const std::string& keyString )
2476 {
2477   DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2478       mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2479
2480   bool update( false );
2481
2482   RemovePreEditStyle();
2483
2484   const std::size_t styledTextSize( mStyledText.size() );
2485   if( styledTextSize >= mMaxStringLength )
2486   {
2487     // Cannot fit these characters into field, clear pre-edit.
2488     if ( mPreEditFlag )
2489     {
2490       mIgnoreCommitFlag = true;
2491       mPreEditFlag = false;
2492     }
2493     EmitMaxInputCharactersReachedSignal();
2494   }
2495   else
2496   {
2497     if( mPreEditFlag )
2498     {
2499       // delete existing pre-edit string
2500       const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2501       mPreEditFlag = false;
2502
2503       DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2504                     (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2505
2506       if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2507       {
2508         // No need to update cursor position as Cursor location given by touch.
2509         InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2510         mPreserveCursorPosition = false;
2511       }
2512       else
2513       {
2514         // Cursor not set by touch so needs to be re-positioned to input more text
2515         mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2516
2517         // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2518         if ( mCommitByKeyInput )
2519         {
2520           mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2521           mCommitByKeyInput = false;
2522         }
2523       }
2524
2525       EmitTextModified();
2526
2527       if ( mSelectTextOnCommit )
2528       {
2529         SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2530       }
2531
2532       update = true;
2533     }
2534     else // mPreEditFlag not set
2535     {
2536       if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2537       {
2538         if( mStyledText.empty() && mPlaceHolderSet )
2539         {
2540           // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2541           mDisplayedTextView.SetText( "" );
2542           mNumberOfSurroundingCharactersDeleted = 0;
2543           mPlaceHolderSet = false;
2544         }
2545         mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2546         update = true;
2547         mNumberOfSurroundingCharactersDeleted = 0;
2548         EmitTextModified();
2549       }
2550       else
2551       {
2552         mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2553       }
2554     }
2555   }
2556
2557   mSelectTextOnCommit = false;
2558
2559   DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2560                                              mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2561
2562   return update;
2563 }
2564
2565 // End of IMF related methods
2566
2567 std::size_t TextInput::DeletePreEdit()
2568 {
2569   DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2570
2571   DALI_ASSERT_DEBUG( mPreEditFlag );
2572
2573   const std::size_t preEditStringLength = mPreEditString.GetLength();
2574   const std::size_t styledTextSize = mStyledText.size();
2575
2576   std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2577
2578   // Prevents erase items outside mStyledText bounds.
2579   if( mPreEditStartPosition > styledTextSize )
2580   {
2581     DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2582     mPreEditStartPosition = styledTextSize;
2583   }
2584
2585   if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2586   {
2587     DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2588     endPosition = styledTextSize;
2589   }
2590
2591   mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2592
2593   // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2594   // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2595   // reuse glyphs.
2596   // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2597
2598   return preEditStringLength;
2599 }
2600
2601 void TextInput::PreEditReset( bool preserveCursorPosition )
2602 {
2603   DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2604                 preserveCursorPosition, mCursorPosition);
2605
2606   // 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.
2607   mPreserveCursorPosition = preserveCursorPosition;
2608
2609   // Reset incase we are in a pre-edit state.
2610   ImfManager imfManager = ImfManager::Get();
2611   if ( imfManager )
2612   {
2613     imfManager.Reset(); // Will trigger a commit message
2614   }
2615 }
2616
2617 void TextInput::CursorUpdate()
2618 {
2619   DrawCursor();
2620
2621   ImfManager imfManager = ImfManager::Get();
2622   if ( imfManager )
2623   {
2624     std::string text( GetText() );
2625     imfManager.SetSurroundingText( text );  // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2626     imfManager.SetCursorPosition ( mCursorPosition );
2627     imfManager.NotifyCursorPosition();
2628   }
2629 }
2630
2631 /* Delete highlighted characters redisplay*/
2632 void TextInput::DeleteHighlightedText( bool inheritStyle )
2633 {
2634   DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2635
2636   if( mHighlightMeshActor )
2637   {
2638     mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2639
2640     MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2641     MarkupProcessor::StyledTextArray::iterator end =  mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2642
2643     // Get the styled text of the characters to be deleted as it may be needed if
2644     // the "exceed the text-input's boundaries" option is disabled.
2645     MarkupProcessor::StyledTextArray styledCharactersToDelete;
2646
2647     styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2648
2649     mStyledText.erase( start, end ); // erase range of characters
2650
2651     // Remove text from TextView and update place holder text if required
2652
2653     // Set the placeholder text only if the styled text is empty.
2654     if( mStyledText.empty() )
2655     {
2656       ShowPlaceholderText( mStyledPlaceHolderText );
2657     }
2658     else
2659     {
2660       const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2661
2662       mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2663
2664       // It may happen than after removing a white space or a new line character,
2665       // two words merge, this new word could be big enough to not fit in its
2666       // current line, so moved to the next one, and make some part of the text to
2667       // exceed the text-input's boundary.
2668       if( !mExceedEnabled )
2669       {
2670         // Get the new text layout after removing some characters.
2671         mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2672
2673         // Get text-input's size.
2674         const Vector3& size = GetControlSize();
2675
2676         if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2677             ( mTextLayoutInfo.mTextSize.height > size.height ) )
2678         {
2679           mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2680
2681           mStyledText.insert( mStyledText.begin() + mCursorPosition,
2682                               styledCharactersToDelete.begin(),
2683                               styledCharactersToDelete.end() );
2684         }
2685       }
2686     }
2687     GetTextLayoutInfo();
2688
2689     RemoveHighlight();
2690
2691     EmitTextModified();
2692
2693     if( inheritStyle )
2694     {
2695       const TextStyle oldInputStyle( mInputStyle );
2696
2697       mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2698
2699       if( oldInputStyle != mInputStyle )
2700       {
2701         // Updates the line height accordingly with the input style.
2702         UpdateLineHeight();
2703
2704         EmitStyleChangedSignal();
2705       }
2706     }
2707   }
2708 }
2709
2710 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2711 {
2712   DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2713   DALI_ASSERT_DEBUG( !mStyledText.empty() );
2714
2715   DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2716
2717
2718   if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2719   {
2720     MarkupProcessor::StyledTextArray::iterator itStart =  mStyledText.begin() + start;
2721     MarkupProcessor::StyledTextArray::iterator itEnd =  mStyledText.begin() + start + ncharacters;
2722
2723     mStyledText.erase(itStart, itEnd);
2724
2725     // update the selection handles if they are visible.
2726     if( mHighlightMeshActor )
2727     {
2728       std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2729       std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2730
2731       if( minHandle >= start + ncharacters )
2732       {
2733         minHandle -= ncharacters;
2734       }
2735       else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2736       {
2737         minHandle = start;
2738       }
2739
2740       if( maxHandle >= start + ncharacters )
2741       {
2742         maxHandle -= ncharacters;
2743       }
2744       else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2745       {
2746         maxHandle = start;
2747       }
2748     }
2749
2750     // 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.
2751   }
2752
2753   DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2754
2755   // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2756   // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2757   // Mean we do not re-draw the text more than we have too.
2758 }
2759
2760 /* Delete character at current cursor position and redisplay*/
2761 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2762 {
2763   // Ensure positionToDelete is not out of bounds.
2764   DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2765   DALI_ASSERT_DEBUG( !mStyledText.empty() );
2766   DALI_ASSERT_DEBUG( positionToDelete > 0 );
2767
2768   DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2769
2770
2771   if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() )  // don't try to delete if no characters left of cursor
2772   {
2773     MarkupProcessor::StyledTextArray::iterator it =  mStyledText.begin() + positionToDelete - 1;
2774
2775     // Get the styled text of the character to be deleted as it may be needed if
2776     // the "exceed the text-input's boundaries" option is disabled.
2777     const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2778
2779     mStyledText.erase(it);  // erase the character left of positionToDelete
2780
2781     if( mStyledText.empty() )
2782     {
2783       ShowPlaceholderText( mStyledPlaceHolderText );
2784     }
2785     else
2786     {
2787       mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2788
2789       const Character characterToDelete = styledCharacterToDelete.mText[0];
2790
2791       // It may happen than after removing a white space or a new line character,
2792       // two words merge, this new word could be big enough to not fit in its
2793       // current line, so moved to the next one, and make some part of the text to
2794       // exceed the text-input's boundary.
2795       if( !mExceedEnabled )
2796       {
2797         if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2798         {
2799           // Get the new text layout after removing one character.
2800           mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2801
2802           // Get text-input's size.
2803           const Vector3& size = GetControlSize();
2804
2805           if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2806               ( mTextLayoutInfo.mTextSize.height > size.height ) )
2807           {
2808             MarkupProcessor::StyledTextArray array;
2809             array.push_back( styledCharacterToDelete );
2810             mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2811
2812             mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2813           }
2814         }
2815       }
2816     }
2817     GetTextLayoutInfo();
2818
2819     ShowGrabHandleAndSetVisibility( false );
2820
2821     mCursorPosition = positionToDelete -1;
2822
2823     const TextStyle oldInputStyle( mInputStyle );
2824
2825     mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2826
2827     if( oldInputStyle != mInputStyle )
2828     {
2829       // Updates the line height accordingly with the input style.
2830       UpdateLineHeight();
2831
2832       EmitStyleChangedSignal();
2833     }
2834   }
2835 }
2836
2837 /*Insert new character into the string and (optionally) redisplay text-input*/
2838 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2839 {
2840   DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2841
2842   // Ensure insertionPosition is not out of bounds.
2843   DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2844
2845   bool textExceedsMaximunNumberOfCharacters = false;
2846   bool textExceedsBoundary = false;
2847   std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2848
2849   ShowGrabHandleAndSetVisibility( false );
2850
2851   if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2852   {
2853     if( mPreEditFlag )
2854     {
2855       mIgnoreCommitFlag = true;
2856       mPreEditFlag = false;
2857       // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2858       // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2859     }
2860
2861     if( textExceedsMaximunNumberOfCharacters )
2862     {
2863       EmitMaxInputCharactersReachedSignal();
2864     }
2865
2866     if( textExceedsBoundary )
2867     {
2868       EmitInputTextExceedsBoundariesSignal();
2869       PreEditReset( false );
2870     }
2871   }
2872
2873   return insertedStringLength;
2874 }
2875
2876 ImageActor TextInput::CreateCursor( const Vector4& color)
2877 {
2878   ImageActor cursor;
2879   cursor = CreateSolidColorActor(color);
2880   cursor.SetName( "Cursor" );
2881
2882   cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2883   cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2884   cursor.SetVisible(false);
2885
2886   return cursor;
2887 }
2888
2889 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2890 {
2891   // As cursor is not moving due to grab handle, handle should be hidden.
2892   ShowGrabHandleAndSetVisibility( false );
2893
2894   bool cursorPositionChanged = false;
2895   if (reverse)
2896   {
2897     if ( mCursorPosition >= places )
2898     {
2899       mCursorPosition = mCursorPosition - places;
2900       cursorPositionChanged = true;
2901     }
2902   }
2903   else
2904   {
2905     if ((mCursorPosition + places) <= mStyledText.size())
2906     {
2907       mCursorPosition = mCursorPosition + places;
2908       cursorPositionChanged = true;
2909     }
2910   }
2911
2912   if( cursorPositionChanged )
2913   {
2914     const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2915
2916     const TextStyle oldInputStyle( mInputStyle );
2917     mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2918
2919     DrawCursor();
2920
2921     if( oldInputStyle != mInputStyle )
2922     {
2923       // Updates the line height accordingly with the input style.
2924       UpdateLineHeight();
2925
2926       EmitStyleChangedSignal();
2927     }
2928
2929     ImfManager imfManager = ImfManager::Get();
2930     if ( imfManager )
2931     {
2932       imfManager.SetCursorPosition ( mCursorPosition );
2933       imfManager.NotifyCursorPosition();
2934     }
2935   }
2936 }
2937
2938 void TextInput::DrawCursor()
2939 {
2940   const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2941
2942   // Get height of cursor and set its size
2943   Size size( CURSOR_THICKNESS, 0.0f );
2944   if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2945   {
2946     size.height = rowRect.height;
2947   }
2948   else
2949   {
2950     // Measure Font so know how big text will be if no initial text to measure.
2951     size.height = mLineHeight;
2952   }
2953
2954   mCursor.SetSize(size);
2955
2956   // If the character is italic then the cursor also tilts.
2957   mCursor.SetOrientation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2958
2959   DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2960
2961   if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2962   {
2963     Vector3 altPosition;    // Alternate (i.e. opposite direction) cursor position.
2964     bool altPositionValid;  // Alternate cursor validity flag.
2965     bool directionRTL;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2966     Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2967
2968     SetAltCursorEnabled( altPositionValid );
2969
2970     if( !altPositionValid )
2971     {
2972       mCursor.SetPosition( position + UI_OFFSET );
2973     }
2974     else
2975     {
2976       size.height *= 0.5f;
2977       mCursor.SetSize(size);
2978       mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2979
2980       // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2981       size.height = rowRect.height * 0.5f;
2982       mCursorRTL.SetSize(size);
2983       mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2984     }
2985
2986     if( IsScrollEnabled() )
2987     {
2988       // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2989       mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2990     }
2991   } // EditMode
2992 }
2993
2994 void TextInput::SetAltCursorEnabled( bool enabled )
2995 {
2996   mCursorRTLEnabled = enabled;
2997   mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2998 }
2999
3000 void TextInput::SetCursorVisibility( bool visible )
3001 {
3002   mCursorVisibility = visible;
3003   mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
3004   mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
3005 }
3006
3007 void TextInput::CreateGrabHandle( Dali::Image image )
3008 {
3009   if ( !mGrabHandle )
3010   {
3011     if ( !image )
3012     {
3013       mGrabHandleImage = ResourceImage::New(DEFAULT_GRAB_HANDLE);
3014     }
3015     else
3016     {
3017       mGrabHandleImage = image;
3018     }
3019
3020     mGrabHandle = ImageActor::New(mGrabHandleImage);
3021     mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
3022     mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
3023
3024     mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
3025
3026     ShowGrabHandleAndSetVisibility( false );
3027
3028     CreateGrabArea( mGrabHandle );
3029
3030     mActiveLayer.Add(mGrabHandle);
3031   }
3032 }
3033
3034 void TextInput::CreateGrabArea( Actor& parent )
3035 {
3036   mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3037   mGrabArea.SetName( "GrabArea" );
3038   mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3039   mGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
3040   mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
3041   mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
3042   mTapDetector.Attach( mGrabArea );
3043   mPanGestureDetector.Attach( mGrabArea );
3044   mLongPressDetector.Attach( mGrabArea );
3045
3046   parent.Add(mGrabArea);
3047 }
3048
3049 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
3050 {
3051   Vector3 actualHandlePosition;
3052
3053   if (mGrabHandle)
3054   {
3055     mActualGrabHandlePosition.x += displacement.x;
3056     mActualGrabHandlePosition.y += displacement.y;
3057
3058     // Grab handle should jump to the nearest character and take cursor with it
3059     std::size_t newCursorPosition = 0;
3060     ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3061
3062     Vector3 altPosition;    // Alternate (i.e. opposite direction) cursor position.
3063     bool altPositionValid;  // Alternate cursor validity flag.
3064     bool directionRTL;      // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3065     actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
3066
3067     if( altPositionValid )
3068     {
3069       // Check which of the positions is the closest.
3070       if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
3071       {
3072         actualHandlePosition = altPosition;
3073       }
3074     }
3075
3076     bool handleVisible = true;
3077
3078     if( IsScrollEnabled() )
3079     {