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