TextSelectionPopup to show Paste in data in Clipboard
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl.cpp
index 6baf19e..621d50e 100644 (file)
 
 // EXTERNAL INCLUDES
 #include <dali/public-api/adaptor-framework/key.h>
+#include <dali/integration-api/debug.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/bidirectional-support.h>
+#include <dali-toolkit/internal/text/character-set-conversion.h>
+#include <dali-toolkit/internal/text/layouts/layout-parameters.h>
+#include <dali-toolkit/internal/text/multi-language-support.h>
+#include <dali-toolkit/internal/text/script-run.h>
+#include <dali-toolkit/internal/text/segmentation.h>
+#include <dali-toolkit/internal/text/shaper.h>
+#include <dali-toolkit/internal/text/text-io.h>
+#include <dali-toolkit/internal/text/text-view.h>
 
 namespace
 {
 
+#if defined(DEBUG_ENABLED)
+  Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
+#endif
+
 /**
  * @brief Some characters can be shaped in more than one glyph.
  * This struct is used to retrieve metrics from these group of glyphs.
@@ -96,21 +112,31 @@ void GetGlyphsMetrics( GlyphIndex glyphIndex,
 
 EventData::EventData( DecoratorPtr decorator )
 : mDecorator( decorator ),
-  mPlaceholderText(),
+  mPlaceholderTextActive(),
+  mPlaceholderTextInactive(),
+  mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
   mEventQueue(),
   mScrollPosition(),
   mState( INACTIVE ),
   mPrimaryCursorPosition( 0u ),
-  mSecondaryCursorPosition( 0u ),
+  mLeftSelectionPosition( 0u ),
+  mRightSelectionPosition( 0u ),
+  mPreEditStartPosition( 0u ),
+  mPreEditLength( 0u ),
+  mIsShowingPlaceholderText( false ),
+  mPreEditFlag( false ),
   mDecoratorUpdated( false ),
   mCursorBlinkEnabled( true ),
   mGrabHandleEnabled( true ),
-  mGrabHandlePopupEnabled( false ),
-  mSelectionEnabled( false ),
+  mGrabHandlePopupEnabled( true ),
+  mSelectionEnabled( true ),
   mHorizontalScrollingEnabled( true ),
   mVerticalScrollingEnabled( false ),
   mUpdateCursorPosition( false ),
-  mScrollAfterUpdateCursorPosition( false )
+  mUpdateLeftSelectionPosition( false ),
+  mUpdateRightSelectionPosition( false ),
+  mScrollAfterUpdatePosition( false ),
+  mScrollAfterDelete( false )
 {}
 
 EventData::~EventData()
@@ -118,14 +144,14 @@ EventData::~EventData()
 
 bool Controller::Impl::ProcessInputEvents()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
   if( NULL == mEventData )
   {
     // Nothing to do if there is no text input.
+    DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
     return false;
   }
 
-  mEventData->mDecoratorUpdated = false;
-
   if( mEventData->mDecorator )
   {
     for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
@@ -134,16 +160,6 @@ bool Controller::Impl::ProcessInputEvents()
     {
       switch( iter->type )
       {
-      case Event::KEYBOARD_FOCUS_GAIN_EVENT:
-      {
-        OnKeyboardFocus( true );
-        break;
-      }
-      case Event::KEYBOARD_FOCUS_LOST_EVENT:
-      {
-        OnKeyboardFocus( false );
-        break;
-      }
       case Event::CURSOR_KEY_EVENT:
       {
         OnCursorKeyEvent( *iter );
@@ -160,8 +176,10 @@ bool Controller::Impl::ProcessInputEvents()
         break;
       }
       case Event::GRAB_HANDLE_EVENT:
+      case Event::LEFT_SELECTION_HANDLE_EVENT:
+      case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
       {
-        OnGrabHandleEvent( *iter );
+        OnHandleEvent( *iter );
         break;
       }
       }
@@ -175,35 +193,233 @@ bool Controller::Impl::ProcessInputEvents()
 
     UpdateCursorPosition();
 
-    if( mEventData->mScrollAfterUpdateCursorPosition )
+    if( mEventData->mScrollAfterUpdatePosition )
     {
-      ScrollToMakeCursorVisible();
-      mEventData->mScrollAfterUpdateCursorPosition = false;
+      const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
+
+      ScrollToMakePositionVisible( primaryCursorPosition );
+      mEventData->mScrollAfterUpdatePosition = false;
     }
 
+    mEventData->mDecoratorUpdated = true;
     mEventData->mUpdateCursorPosition = false;
   }
+  else if( mEventData->mScrollAfterDelete )
+  {
+    ScrollTextToMatchCursor();
+    mEventData->mDecoratorUpdated = true;
+    mEventData->mScrollAfterDelete = false;
+  }
+  else
+  {
+    bool leftScroll = false;
+    bool rightScroll = false;
+
+    if( mEventData->mUpdateLeftSelectionPosition )
+    {
+      UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
+
+      if( mEventData->mScrollAfterUpdatePosition )
+      {
+        const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE );
+
+        ScrollToMakePositionVisible( leftHandlePosition );
+        leftScroll = true;
+      }
+
+      mEventData->mDecoratorUpdated = true;
+      mEventData->mUpdateLeftSelectionPosition = false;
+    }
+
+    if( mEventData->mUpdateRightSelectionPosition )
+    {
+      UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
+
+      if( mEventData->mScrollAfterUpdatePosition )
+      {
+        const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE );
+
+        ScrollToMakePositionVisible( rightHandlePosition );
+        rightScroll = true;
+      }
+
+      mEventData->mDecoratorUpdated = true;
+      mEventData->mUpdateRightSelectionPosition = false;
+    }
+
+    if( leftScroll || rightScroll )
+    {
+      mEventData->mScrollAfterUpdatePosition = false;
+    }
+  }
 
   mEventData->mEventQueue.clear();
 
-  return mEventData->mDecoratorUpdated;
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
+
+  const bool decoratorUpdated = mEventData->mDecoratorUpdated;
+  mEventData->mDecoratorUpdated = false;
+
+  return decoratorUpdated;
 }
 
-void Controller::Impl::OnKeyboardFocus( bool hasFocus )
+void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
 {
-  if( NULL == mEventData )
+  // Calculate the operations to be done.
+  const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
+
+  Vector<Character>& utf32Characters = mLogicalModel->mText;
+
+  const Length numberOfCharacters = utf32Characters.Count();
+
+  Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
+  if( GET_LINE_BREAKS & operations )
   {
-    // Nothing to do if there is no text input.
-    return;
+    // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
+    // calculate the bidirectional info for each 'paragraph'.
+    // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
+    // is not shaped together).
+    lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
+
+    SetLineBreakInfo( utf32Characters,
+                      lineBreakInfo );
   }
 
-  if( !hasFocus )
+  Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
+  if( GET_WORD_BREAKS & operations )
   {
-    ChangeState( EventData::INACTIVE );
+    // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
+    wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
+
+    SetWordBreakInfo( utf32Characters,
+                      wordBreakInfo );
   }
-  else
+
+  const bool getScripts = GET_SCRIPTS & operations;
+  const bool validateFonts = VALIDATE_FONTS & operations;
+
+  Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
+  Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
+
+  if( getScripts || validateFonts )
   {
-    ChangeState( EventData::EDITING );
+    // Validates the fonts assigned by the application or assigns default ones.
+    // It makes sure all the characters are going to be rendered by the correct font.
+    MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
+
+    if( getScripts )
+    {
+      // Retrieves the scripts used in the text.
+      multilanguageSupport.SetScripts( utf32Characters,
+                                       lineBreakInfo,
+                                       scripts );
+    }
+
+    if( validateFonts )
+    {
+      if( 0u == validFonts.Count() )
+      {
+        // Copy the requested font defaults received via the property system.
+        // These may not be valid i.e. may not contain glyphs for the necessary scripts.
+        GetDefaultFonts( validFonts, numberOfCharacters );
+      }
+
+      // Validates the fonts. If there is a character with no assigned font it sets a default one.
+      // After this call, fonts are validated.
+      multilanguageSupport.ValidateFonts( utf32Characters,
+                                          scripts,
+                                          validFonts );
+    }
+  }
+
+  Vector<Character> mirroredUtf32Characters;
+  bool textMirrored = false;
+  if( BIDI_INFO & operations )
+  {
+    // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
+    // bidirectional info.
+
+    Length numberOfParagraphs = 0u;
+
+    const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
+    for( Length index = 0u; index < numberOfCharacters; ++index )
+    {
+      if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
+      {
+        ++numberOfParagraphs;
+      }
+    }
+
+    Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
+    bidirectionalInfo.Reserve( numberOfParagraphs );
+
+    // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
+    SetBidirectionalInfo( utf32Characters,
+                          scripts,
+                          lineBreakInfo,
+                          bidirectionalInfo );
+
+    if( 0u != bidirectionalInfo.Count() )
+    {
+      // This paragraph has right to left text. Some characters may need to be mirrored.
+      // TODO: consider if the mirrored string can be stored as well.
+
+      textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
+
+      // Only set the character directions if there is right to left characters.
+      Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
+      directions.Resize( numberOfCharacters );
+
+      GetCharactersDirection( bidirectionalInfo,
+                              directions );
+    }
+    else
+    {
+      // There is no right to left characters. Clear the directions vector.
+      mLogicalModel->mCharacterDirections.Clear();
+    }
+
+   }
+
+  Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
+  Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
+  Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
+  if( SHAPE_TEXT & operations )
+  {
+    const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
+    // Shapes the text.
+    ShapeText( textToShape,
+               lineBreakInfo,
+               scripts,
+               validFonts,
+               glyphs,
+               glyphsToCharactersMap,
+               charactersPerGlyph );
+
+    // Create the 'number of glyphs' per character and the glyph to character conversion tables.
+    mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
+    mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
+  }
+
+  const Length numberOfGlyphs = glyphs.Count();
+
+  if( GET_GLYPH_METRICS & operations )
+  {
+    mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
+  }
+}
+
+void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
+{
+  if( mFontDefaults )
+  {
+    FontRun fontRun;
+    fontRun.characterRun.characterIndex = 0;
+    fontRun.characterRun.numberOfCharacters = numberOfCharacters;
+    fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
+    fontRun.isDefault = true;
+
+    fonts.PushBack( fontRun );
   }
 }
 
@@ -241,48 +457,47 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event )
   }
 
   mEventData->mUpdateCursorPosition = true;
-  mEventData->mScrollAfterUpdateCursorPosition = true;
-}
-
-void Controller::Impl::HandleCursorKey( int keyCode )
-{
-  // TODO
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
+  mEventData->mScrollAfterUpdatePosition = true;
 }
 
 void Controller::Impl::OnTapEvent( const Event& event )
 {
-  if( NULL == mEventData )
+  if( NULL != mEventData )
   {
-    // Nothing to do if there is no text input.
-    return;
-  }
+    const unsigned int tapCount = event.p1.mUint;
 
-  const unsigned int tapCount = event.p1.mUint;
-
-  if( 1u == tapCount )
-  {
-    ChangeState( EventData::EDITING );
+    if( 1u == tapCount )
+    {
+      if( ! IsShowingPlaceholderText() )
+      {
+        const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
+        const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
 
-    const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
-    const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
+        mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
+                                                                    yPosition );
+      }
+      else
+      {
+        mEventData->mPrimaryCursorPosition = 0u;
+      }
 
-    mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
-                                                                yPosition );
+      mEventData->mUpdateCursorPosition = true;
+      mEventData->mScrollAfterUpdatePosition = true;
+    }
+    else if( mEventData->mSelectionEnabled &&
+             ( 2u == tapCount ) )
+    {
+      // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
+      const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
+      const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
 
-    mEventData->mUpdateCursorPosition = true;
-    mEventData->mScrollAfterUpdateCursorPosition = true;
-  }
-  else if( mEventData->mSelectionEnabled &&
-           ( 2u == tapCount ) )
-  {
-    ChangeState( EventData::SELECTING );
+      RepositionSelectionHandles( xPosition,
+                                  yPosition );
 
-    RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
+      mEventData->mScrollAfterUpdatePosition = true;
+      mEventData->mUpdateLeftSelectionPosition = true;
+      mEventData->mUpdateRightSelectionPosition = true;
+    }
   }
 }
 
@@ -325,7 +540,7 @@ void Controller::Impl::OnPanEvent( const Event& event )
   }
 }
 
-void Controller::Impl::OnGrabHandleEvent( const Event& event )
+void Controller::Impl::OnHandleEvent( const Event& event )
 {
   if( NULL == mEventData )
   {
@@ -333,46 +548,119 @@ void Controller::Impl::OnGrabHandleEvent( const Event& event )
     return;
   }
 
-  unsigned int state = event.p1.mUint;
+  const unsigned int state = event.p1.mUint;
+  const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
 
-  if( GRAB_HANDLE_PRESSED == state )
+  if( HANDLE_PRESSED == state )
   {
     // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
     const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
     const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
 
-    //mDecorator->HidePopup();
-    ChangeState ( EventData::EDITING );
+    const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
 
-    const CharacterIndex newCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
+    if( Event::GRAB_HANDLE_EVENT == event.type )
+    {
+      ChangeState ( EventData::GRAB_HANDLE_PANNING );
 
-    if( newCursorPosition != mEventData->mPrimaryCursorPosition )
+      if( handleNewPosition != mEventData->mPrimaryCursorPosition )
+      {
+        mEventData->mPrimaryCursorPosition = handleNewPosition;
+        mEventData->mUpdateCursorPosition = true;
+      }
+    }
+    else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
     {
-      mEventData->mPrimaryCursorPosition = newCursorPosition;
-      mEventData->mUpdateCursorPosition = true;
+      ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+      if( handleNewPosition != mEventData->mLeftSelectionPosition )
+      {
+        mEventData->mLeftSelectionPosition = handleNewPosition;
+
+        RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
+                                    mEventData->mRightSelectionPosition );
+
+        mEventData->mUpdateLeftSelectionPosition = true;
+      }
     }
-  }
-  else if( mEventData->mGrabHandlePopupEnabled &&
-           ( ( GRAB_HANDLE_RELEASED == state ) ||
-             ( GRAB_HANDLE_STOP_SCROLLING == state ) ) )
-  {
-    //mDecorator->ShowPopup();
-    ChangeState ( EventData::EDITING_WITH_POPUP );
-    mEventData->mUpdateCursorPosition = true;
-    mEventData->mDecoratorUpdated = true;
+    else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+    {
+      ChangeState ( EventData::SELECTION_HANDLE_PANNING );
+
+      if( handleNewPosition != mEventData->mRightSelectionPosition )
+      {
+        mEventData->mRightSelectionPosition = handleNewPosition;
+
+        RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
+                                    mEventData->mRightSelectionPosition );
 
-    if( GRAB_HANDLE_STOP_SCROLLING == state )
+        mEventData->mUpdateRightSelectionPosition = true;
+      }
+    }
+  } // end ( HANDLE_PRESSED == state )
+  else if( ( HANDLE_RELEASED == state ) ||
+           handleStopScrolling )
+  {
+    CharacterIndex handlePosition = 0u;
+    if( handleStopScrolling )
     {
       // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
       const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
       const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
 
-      mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
+      handlePosition = GetClosestCursorIndex( xPosition, yPosition );
+    }
+
+    if( Event::GRAB_HANDLE_EVENT == event.type )
+    {
+      mEventData->mUpdateCursorPosition = true;
+
+      ChangeState( EventData::EDITING_WITH_POPUP );
 
-      mEventData->mScrollAfterUpdateCursorPosition = true;
+      if( handleStopScrolling )
+      {
+        mEventData->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition;
+        mEventData->mPrimaryCursorPosition = handlePosition;
+      }
     }
-  }
-  else if( GRAB_HANDLE_SCROLLING == state )
+    else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
+    {
+      ChangeState( EventData::SELECTING );
+
+      if( handleStopScrolling )
+      {
+        mEventData->mUpdateLeftSelectionPosition = mEventData->mLeftSelectionPosition != handlePosition;
+        mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition;
+        mEventData->mLeftSelectionPosition = handlePosition;
+
+        if( mEventData->mUpdateLeftSelectionPosition )
+        {
+          RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
+                                      mEventData->mRightSelectionPosition );
+        }
+      }
+    }
+    else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
+    {
+      ChangeState( EventData::SELECTING );
+
+      if( handleStopScrolling )
+      {
+        mEventData->mUpdateRightSelectionPosition = mEventData->mRightSelectionPosition != handlePosition;
+        mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition;
+        mEventData->mRightSelectionPosition = handlePosition;
+
+        if( mEventData->mUpdateRightSelectionPosition )
+        {
+          RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
+                                      mEventData->mRightSelectionPosition );
+        }
+      }
+    }
+
+    mEventData->mDecoratorUpdated = true;
+  } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
+  else if( HANDLE_SCROLLING == state )
   {
     const float xSpeed = event.p2.mFloat;
     const Vector2& actualSize = mVisualModel->GetActualSize();
@@ -380,43 +668,152 @@ void Controller::Impl::OnGrabHandleEvent( const Event& event )
     mEventData->mScrollPosition.x += xSpeed;
 
     ClampHorizontalScroll( actualSize );
-  }
+
+    const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
+    const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
+
+    if( Event::GRAB_HANDLE_EVENT == event.type )
+    {
+      ChangeState( EventData::GRAB_HANDLE_PANNING );
+    }
+    else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
+    {
+      // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles.
+      //       Think if something can be done to save power.
+
+      ChangeState( EventData::SELECTION_HANDLE_PANNING );
+
+      const Vector2& position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
+
+      // Get the new handle position.
+      // The selection handle's position is in decorator coords. Need to transforms to text coords.
+      const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x,
+                                                                   position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y );
+
+      if( leftSelectionHandleEvent )
+      {
+        mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition;
+        mEventData->mLeftSelectionPosition = handlePosition;
+      }
+      else
+      {
+        mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition;
+        mEventData->mRightSelectionPosition = handlePosition;
+      }
+
+      if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition )
+      {
+        RepositionSelectionHandles( mEventData->mLeftSelectionPosition,
+                                    mEventData->mRightSelectionPosition );
+      }
+    }
+    mEventData->mDecoratorUpdated = true;
+  } // end ( HANDLE_SCROLLING == state )
 }
 
-void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
+void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd )
 {
-  if( NULL == mEventData )
+  if( selectionStart == selectionEnd )
   {
-    // Nothing to do if there is no text input.
+    // Nothing to select if handles are in the same place.
     return;
   }
 
-  // TODO - Find which word was selected
+  mEventData->mDecorator->ClearHighlights();
 
-  const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
-  const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
+  mEventData->mLeftSelectionPosition = selectionStart;
+  mEventData->mRightSelectionPosition = selectionEnd;
 
-  const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
-  const Vector<Vector2>::SizeType positionCount = positions.Count();
+  const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
+  const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
+  const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin();
+  const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin();
+
+  // TODO: Better algorithm to create the highlight box.
+  // TODO: Multi-line.
+
+  const Vector<LineRun>& lines = mVisualModel->mLines;
+  const LineRun& firstLine = *lines.Begin();
+  const float height = firstLine.ascender + -firstLine.descender;
+
+  const bool indicesSwapped = ( selectionStart > selectionEnd );
+  if( indicesSwapped )
+  {
+    std::swap( selectionStart, selectionEnd );
+  }
+
+  GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
+  GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u;
 
-  // Guard against glyphs which did not fit inside the layout
-  const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
+  mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
 
-  if( count )
+  const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
+
+  for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
   {
-    float primaryX   = positions[0].x + mEventData->mScrollPosition.x;
-    float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
+    const GlyphInfo& glyph = *( glyphsBuffer + index );
+    const Vector2& position = *( positionsBuffer + index );
+
+    const float xPosition = position.x - glyph.xBearing + offset.x;
+    mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height );
+  }
+
+  CursorInfo primaryCursorInfo;
+  GetCursorPosition( mEventData->mLeftSelectionPosition,
+                     primaryCursorInfo );
+
+  CursorInfo secondaryCursorInfo;
+  GetCursorPosition( mEventData->mRightSelectionPosition,
+                     secondaryCursorInfo );
+
+  const Vector2 primaryPosition = primaryCursorInfo.primaryPosition + offset;
+  const Vector2 secondaryPosition = secondaryCursorInfo.primaryPosition + offset;
+
+  mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryPosition.x, primaryPosition.y, primaryCursorInfo.lineHeight );
 
-    // TODO - multi-line selection
-    const Vector<LineRun>& lines = mVisualModel->mLines;
-    float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
+  mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight );
+
+  // Set the flag to update the decorator.
+  mEventData->mDecoratorUpdated = true;
+}
+
+void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
+{
+  if( NULL == mEventData )
+  {
+    // Nothing to do if there is no text input.
+    return;
+  }
+
+  if( IsShowingPlaceholderText() )
+  {
+    // Nothing to do if there is the place-holder text.
+    return;
+  }
+
+  const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
+  const Length numberOfLines  = mVisualModel->mLines.Count();
+  if( 0 == numberOfGlyphs ||
+      0 == numberOfLines )
+  {
+    // Nothing to do if there is no text.
+    return;
+  }
 
-    mEventData->mDecorator->SetPosition( PRIMARY_SELECTION_HANDLE,     primaryX, mEventData->mScrollPosition.y, height );
-    mEventData->mDecorator->SetPosition( SECONDARY_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
+  // Find which word was selected
+  CharacterIndex selectionStart( 0 );
+  CharacterIndex selectionEnd( 0 );
+  FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd );
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd );
 
-    mEventData->mDecorator->ClearHighlights();
-    mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
+  if( selectionStart == selectionEnd )
+  {
+    ChangeState( EventData::EDITING );
+    // Nothing to select. i.e. a white space, out of bounds
+    return;
   }
+
+  RepositionSelectionHandles( selectionStart, selectionEnd );
 }
 
 void Controller::Impl::ChangeState( EventData::State newState )
@@ -435,8 +832,9 @@ void Controller::Impl::ChangeState( EventData::State newState )
     {
       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
       mEventData->mDecorator->StopCursorBlink();
-      mEventData->mDecorator->SetGrabHandleActive( false );
-      mEventData->mDecorator->SetSelectionActive( false );
+      mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
       mEventData->mDecorator->SetPopupActive( false );
       mEventData->mDecoratorUpdated = true;
     }
@@ -444,8 +842,34 @@ void Controller::Impl::ChangeState( EventData::State newState )
     {
       mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
       mEventData->mDecorator->StopCursorBlink();
-      mEventData->mDecorator->SetGrabHandleActive( false );
-      mEventData->mDecorator->SetSelectionActive( true );
+      mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
+      mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
+      if( mEventData->mGrabHandlePopupEnabled )
+      {
+        TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons(  TextSelectionPopup::CUT | TextSelectionPopup::COPY );
+        if ( !IsClipboardEmpty() )
+        {
+          buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
+        }
+
+        mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
+        mEventData->mDecorator->SetPopupActive( true );
+      }
+      mEventData->mDecoratorUpdated = true;
+    }
+    else if ( EventData::SELECTION_CHANGED  == mEventData->mState )
+    {
+      if( mEventData->mGrabHandlePopupEnabled )
+      {
+        TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons(  TextSelectionPopup::CUT | TextSelectionPopup::COPY );
+        if (  !IsClipboardEmpty() )
+        {
+          buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
+        }
+        mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
+        mEventData->mDecorator->SetPopupActive( true );
+      }
       mEventData->mDecoratorUpdated = true;
     }
     else if( EventData::EDITING == mEventData->mState )
@@ -455,15 +879,14 @@ void Controller::Impl::ChangeState( EventData::State newState )
       {
         mEventData->mDecorator->StartCursorBlink();
       }
-      if( mEventData->mGrabHandleEnabled )
-      {
-        mEventData->mDecorator->SetGrabHandleActive( true );
-      }
+      // Grab handle is not shown until a tap is received whilst EDITING
+      mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
       if( mEventData->mGrabHandlePopupEnabled )
       {
         mEventData->mDecorator->SetPopupActive( false );
       }
-      mEventData->mDecorator->SetSelectionActive( false );
       mEventData->mDecoratorUpdated = true;
     }
     else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
@@ -473,15 +896,56 @@ void Controller::Impl::ChangeState( EventData::State newState )
       {
         mEventData->mDecorator->StartCursorBlink();
       }
-      if( mEventData->mGrabHandleEnabled )
+      if( mEventData->mSelectionEnabled )
+      {
+        mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+        mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
+      }
+      else
       {
-        mEventData->mDecorator->SetGrabHandleActive( true );
+        mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
       }
       if( mEventData->mGrabHandlePopupEnabled )
       {
+        TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
+
+        if ( !IsClipboardEmpty() )
+        {
+          buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
+        }
+
+        mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
         mEventData->mDecorator->SetPopupActive( true );
       }
-      mEventData->mDecorator->SetSelectionActive( false );
+      mEventData->mDecoratorUpdated = true;
+    }
+    else if ( EventData::SELECTION_HANDLE_PANNING == mEventData->mState )
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
+      mEventData->mDecorator->StopCursorBlink();
+      mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
+      mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
+      if( mEventData->mGrabHandlePopupEnabled )
+      {
+        mEventData->mDecorator->SetPopupActive( false );
+      }
+      mEventData->mDecoratorUpdated = true;
+    }
+    else if ( EventData::GRAB_HANDLE_PANNING == mEventData->mState )
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+      if( mEventData->mCursorBlinkEnabled )
+      {
+        mEventData->mDecorator->StartCursorBlink();
+      }
+      mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
+      mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+      mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
+      if( mEventData->mGrabHandlePopupEnabled )
+      {
+        mEventData->mDecorator->SetPopupActive( false );
+      }
       mEventData->mDecoratorUpdated = true;
     }
   }
@@ -505,9 +969,49 @@ LineIndex Controller::Impl::GetClosestLine( float y ) const
     }
   }
 
+  if( lineIndex == 0 )
+  {
+    return 0;
+  }
+
   return lineIndex-1;
 }
 
+void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex )
+{
+  CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY );
+  if( hitCharacter >= mLogicalModel->mText.Count() )
+  {
+    // Selection out of bounds.
+    return;
+  }
+
+  startIndex = hitCharacter;
+  endIndex = hitCharacter;
+
+  if( !TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ) )
+  {
+    // Find the start and end of the text
+    for( startIndex = hitCharacter; startIndex > 0; --startIndex )
+    {
+      Character charCode = mLogicalModel->mText[ startIndex-1 ];
+      if( TextAbstraction::IsWhiteSpace( charCode ) )
+      {
+        break;
+      }
+    }
+    const CharacterIndex pastTheEnd = mLogicalModel->mText.Count();
+    for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
+    {
+      Character charCode = mLogicalModel->mText[ endIndex ];
+      if( TextAbstraction::IsWhiteSpace( charCode ) )
+      {
+        break;
+      }
+    }
+  }
+}
+
 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
                                                         float visualY )
 {
@@ -578,9 +1082,10 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
 
     const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
 
-    const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
+    // Find the mid-point of the area containing the glyph
+    const float glyphCenter = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
 
-    if( visualX < glyphX )
+    if( visualX < glyphCenter )
     {
       matched = true;
       break;
@@ -594,7 +1099,9 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
     visualIndex = endCharacter;
   }
 
-  return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
+  logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex );
+  return logicalIndex;
 }
 
 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
@@ -831,38 +1338,151 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index )
 
 void Controller::Impl::UpdateCursorPosition()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
   if( NULL == mEventData )
   {
     // Nothing to do if there is no text input.
+    DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
     return;
   }
 
-  CursorInfo cursorInfo;
-  GetCursorPosition( mEventData->mPrimaryCursorPosition,
-                     cursorInfo );
+  if( IsShowingPlaceholderText() )
+  {
+    // Do not want to use the place-holder text to set the cursor position.
 
-  mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
-                                       cursorInfo.primaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
-                                       cursorInfo.primaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
-                                       cursorInfo.primaryCursorHeight,
-                                       cursorInfo.lineHeight );
+    // Use the line's height of the font's family set to set the cursor's size.
+    // If there is no font's family set, use the default font.
+    // Use the current alignment to place the cursor at the beginning, center or end of the box.
 
-  if( cursorInfo.isSecondaryCursor )
+    float lineHeight = 0.f;
+
+    FontId defaultFontId = 0u;
+    if( NULL == mFontDefaults )
+    {
+      defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
+                                             EMPTY_STRING );
+    }
+    else
+    {
+      defaultFontId = mFontDefaults->GetFontId( mFontClient );
+    }
+
+    Text::FontMetrics fontMetrics;
+    mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
+
+    lineHeight = fontMetrics.ascender - fontMetrics.descender;
+
+
+    Vector2 cursorPosition;
+
+    switch( mLayoutEngine.GetHorizontalAlignment() )
+    {
+      case LayoutEngine::HORIZONTAL_ALIGN_BEGIN:
+      {
+        cursorPosition.x = 1.f;
+        break;
+      }
+      case LayoutEngine::HORIZONTAL_ALIGN_CENTER:
+      {
+        cursorPosition.x = floor( 0.5f * mControlSize.width );
+        break;
+      }
+      case LayoutEngine::HORIZONTAL_ALIGN_END:
+      {
+        cursorPosition.x = mControlSize.width;
+        break;
+      }
+    }
+
+    switch( mLayoutEngine.GetVerticalAlignment() )
+    {
+      case LayoutEngine::VERTICAL_ALIGN_TOP:
+      {
+        cursorPosition.y = 0.f;
+        break;
+      }
+      case LayoutEngine::VERTICAL_ALIGN_CENTER:
+      {
+        cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) );
+        break;
+      }
+      case LayoutEngine::VERTICAL_ALIGN_BOTTOM:
+      {
+        cursorPosition.y = mControlSize.height - lineHeight;
+        break;
+      }
+    }
+
+    mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
+                                         cursorPosition.x,
+                                         cursorPosition.y,
+                                         lineHeight,
+                                         lineHeight );
+  }
+  else
   {
-    mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
-    mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
-                                         cursorInfo.secondaryPosition.x + mEventData->mScrollPosition.x + mAlignmentOffset.x,
-                                         cursorInfo.secondaryPosition.y + mEventData->mScrollPosition.y + mAlignmentOffset.y,
-                                         cursorInfo.secondaryCursorHeight,
+    CursorInfo cursorInfo;
+    GetCursorPosition( mEventData->mPrimaryCursorPosition,
+                       cursorInfo );
+
+    const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
+    const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
+
+    // Sets the cursor position.
+    mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
+                                         cursorPosition.x,
+                                         cursorPosition.y,
+                                         cursorInfo.primaryCursorHeight,
                                          cursorInfo.lineHeight );
+    DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
+
+    // Sets the grab handle position.
+    mEventData->mDecorator->SetPosition( GRAB_HANDLE,
+                                         cursorPosition.x,
+                                         cursorPosition.y,
+                                         cursorInfo.lineHeight );
+
+    if( cursorInfo.isSecondaryCursor )
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
+      mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
+                                           cursorInfo.secondaryPosition.x + offset.x,
+                                           cursorInfo.secondaryPosition.y + offset.y,
+                                           cursorInfo.secondaryCursorHeight,
+                                           cursorInfo.lineHeight );
+      DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
+    }
+    else
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+    }
   }
-  else
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
+}
+
+void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
+{
+  if( ( LEFT_SELECTION_HANDLE != handleType ) &&
+      ( RIGHT_SELECTION_HANDLE != handleType ) )
   {
-    mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+    return;
   }
 
-  mEventData->mUpdateCursorPosition = false;
-  mEventData->mDecoratorUpdated = true;
+  const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
+  const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
+
+  CursorInfo cursorInfo;
+  GetCursorPosition( index,
+                     cursorInfo );
+
+  const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
+  const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
+
+  // Sets the grab handle position.
+  mEventData->mDecorator->SetPosition( handleType,
+                                       cursorPosition.x,
+                                       cursorPosition.y,
+                                       cursorInfo.lineHeight );
 }
 
 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
@@ -899,27 +1519,19 @@ void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
   }
 }
 
-void Controller::Impl::ScrollToMakeCursorVisible()
+void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position )
 {
-  if( NULL == mEventData )
-  {
-    // Nothing to do if there is no text input.
-    return;
-  }
-
-  const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
-
   Vector2 offset;
   bool updateDecorator = false;
-  if( primaryCursorPosition.x < 0.f )
+  if( position.x < 0.f )
   {
-    offset.x = -primaryCursorPosition.x;
+    offset.x = -position.x;
     mEventData->mScrollPosition.x += offset.x;
     updateDecorator = true;
   }
-  else if( primaryCursorPosition.x > mControlSize.width )
+  else if( position.x > mControlSize.width )
   {
-    offset.x = mControlSize.width - primaryCursorPosition.x;
+    offset.x = mControlSize.width - position.x;
     mEventData->mScrollPosition.x += offset.x;
     updateDecorator = true;
   }
@@ -932,6 +1544,57 @@ void Controller::Impl::ScrollToMakeCursorVisible()
   // TODO : calculate the vertical scroll.
 }
 
+void Controller::Impl::ScrollTextToMatchCursor()
+{
+  // Get the current cursor position in decorator coords.
+  const Vector2& currentCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
+
+  // Calculate the new cursor position.
+  CursorInfo cursorInfo;
+  GetCursorPosition( mEventData->mPrimaryCursorPosition,
+                     cursorInfo );
+
+  // Calculate the offset to match the cursor position before the character was deleted.
+  mEventData->mScrollPosition.x = currentCursorPosition.x - cursorInfo.primaryPosition.x - mAlignmentOffset.x;
+
+  ClampHorizontalScroll( mVisualModel->GetActualSize() );
+  bool updateCursorPosition = true;
+
+  const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
+  const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
+
+  if( updateCursorPosition )
+  {
+    // Sets the cursor position.
+    mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
+                                         cursorPosition.x,
+                                         cursorPosition.y,
+                                         cursorInfo.primaryCursorHeight,
+                                         cursorInfo.lineHeight );
+
+    // Sets the grab handle position.
+    mEventData->mDecorator->SetPosition( GRAB_HANDLE,
+                                         cursorPosition.x,
+                                         cursorPosition.y,
+                                         cursorInfo.lineHeight );
+
+    if( cursorInfo.isSecondaryCursor )
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
+      mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
+                                           cursorInfo.secondaryPosition.x + offset.x,
+                                           cursorInfo.secondaryPosition.y + offset.y,
+                                           cursorInfo.secondaryCursorHeight,
+                                           cursorInfo.lineHeight );
+      DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
+    }
+    else
+    {
+      mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+    }
+  }
+}
+
 void Controller::Impl::RequestRelayout()
 {
   mControlInterface.RequestTextRelayout();