+ const bool indicesSwapped = ( selectionStart > selectionEnd );
+ if( indicesSwapped )
+ {
+ std::swap( selectionStart, selectionEnd );
+ }
+
+ const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart );
+ const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) );
+ const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u );
+
+ mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped );
+
+ const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
+
+ for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index )
+ {
+ // TODO: Fix the LATIN ligatures. i.e ff, fi, etc...
+ 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 );
+
+ 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;
+ }
+
+ // 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 );
+
+ if( selectionStart == selectionEnd )
+ {
+ ChangeState( EventData::EDITING );
+ // Nothing to select. i.e. a white space, out of bounds
+ return;
+ }
+
+ RepositionSelectionHandles( selectionStart, selectionEnd );
+}
+
+void Controller::Impl::SetPopupButtons()
+{
+ /**
+ * Sets the Popup buttons to be shown depending on State.
+ *
+ * If SELECTING : CUT & COPY + ( PASTE & CLIPBOARD if content available to paste )
+ *
+ * If EDITING_WITH_POPUP : SELECT & SELECT_ALL
+ */
+
+ TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE;
+
+ if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) )
+ {
+ buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY );
+
+ if ( !IsClipboardEmpty() )
+ {
+ buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
+ buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
+ }
+
+ if ( !mEventData->mAllTextSelected )
+ {
+ buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::SELECT_ALL ) );
+ }
+ }
+ else if ( EventData::EDITING_WITH_POPUP == mEventData->mState )
+ {
+ if ( mLogicalModel->mText.Count() && !IsShowingPlaceholderText())
+ {
+ buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::SELECT | TextSelectionPopup::SELECT_ALL );
+ }
+
+ if ( !IsClipboardEmpty() )
+ {
+ buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) );
+ buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) );
+ }
+ }
+
+ mEventData->mDecorator->SetEnabledPopupButtons( buttonsToShow );
+}
+
+void Controller::Impl::ChangeState( EventData::State newState )
+{
+ if( NULL == mEventData )
+ {
+ // Nothing to do if there is no text input.
+ return;
+ }
+
+ if( mEventData->mState != newState )
+ {
+ mEventData->mState = newState;
+
+ if( EventData::INACTIVE == mEventData->mState )
+ {
+ mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
+ mEventData->mDecorator->StopCursorBlink();
+ 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;
+ HideClipboard();
+ }
+ else if ( EventData::INTERRUPTED == mEventData->mState)
+ {
+ 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;
+ HideClipboard();
+ }
+ else if ( EventData::SELECTING == 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 )
+ {
+ SetPopupButtons();
+ mEventData->mDecorator->SetPopupActive( true );
+ }
+ mEventData->mDecoratorUpdated = true;
+ }
+ else if ( EventData::SELECTION_CHANGED == mEventData->mState )
+ {
+ if( mEventData->mGrabHandlePopupEnabled )
+ {
+ SetPopupButtons();
+ mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
+ mEventData->mDecorator->SetPopupActive( true );
+ }
+ mEventData->mDecoratorUpdated = true;
+ }
+ else if( EventData::EDITING == mEventData->mState )
+ {
+ mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+ if( mEventData->mCursorBlinkEnabled )
+ {
+ mEventData->mDecorator->StartCursorBlink();
+ }
+ // 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->mDecoratorUpdated = true;
+ HideClipboard();
+ }
+ else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
+ {
+ mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+ if( mEventData->mCursorBlinkEnabled )
+ {
+ mEventData->mDecorator->StartCursorBlink();
+ }
+ if( mEventData->mSelectionEnabled )
+ {
+ mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
+ mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
+ }
+ else
+ {
+ mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
+ }
+ if( mEventData->mGrabHandlePopupEnabled )
+ {
+ SetPopupButtons();
+ mEventData->mDecorator->SetPopupActive( true );
+ }
+ HideClipboard();
+ mEventData->mDecoratorUpdated = true;
+ }
+ else if( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState )
+ {
+ mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
+ if( mEventData->mCursorBlinkEnabled )
+ {
+ mEventData->mDecorator->StartCursorBlink();
+ }
+ // Grab handle is not shown until a tap is received whilst EDITING
+ 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;
+ HideClipboard();
+ }
+ 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;
+ }
+ }
+}
+
+LineIndex Controller::Impl::GetClosestLine( float y ) const
+{
+ float totalHeight = 0.f;
+ LineIndex lineIndex = 0u;
+
+ const Vector<LineRun>& lines = mVisualModel->mLines;
+ for( LineIndex endLine = lines.Count();
+ lineIndex < endLine;
+ ++lineIndex )
+ {
+ const LineRun& lineRun = lines[lineIndex];
+ totalHeight += lineRun.ascender + -lineRun.descender;
+ if( y < totalHeight )
+ {
+ return lineIndex;
+ }
+ }
+
+ 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 )
+{
+ if( NULL == mEventData )
+ {
+ // Nothing to do if there is no text input.
+ return 0u;
+ }
+
+ CharacterIndex logicalIndex = 0u;
+
+ const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
+ const Length numberOfLines = mVisualModel->mLines.Count();
+ if( 0 == numberOfGlyphs ||
+ 0 == numberOfLines )
+ {
+ return logicalIndex;
+ }
+
+ // Find which line is closest
+ const LineIndex lineIndex = GetClosestLine( visualY );
+ const LineRun& line = mVisualModel->mLines[lineIndex];
+
+ // Get the positions of the glyphs.
+ const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
+ const Vector2* const positionsBuffer = positions.Begin();
+
+ // Get the visual to logical conversion tables.
+ const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
+ const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
+
+ // Get the character to glyph conversion table.
+ const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
+
+ // Get the glyphs per character table.
+ const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
+ const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
+
+ // If the vector is void, there is no right to left characters.
+ const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
+
+ const CharacterIndex startCharacter = line.characterRun.characterIndex;
+ const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
+ DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
+
+ // Whether there is a hit on a glyph.
+ bool matched = false;