X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=b49ea43dbceb7865858ce7fb78cbba721bb9ad5c;hp=18d79302e991b503b7e24745ab3d3df1ee415fa4;hb=ca0909c54cdc7ba39ae8916352cabe078c060626;hpb=c5867682354d37755781047add02d85e46078a47 diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 18d7930..b49ea43 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -81,8 +81,8 @@ namespace Text * @param[in] glyphIndex The index to the first glyph. * @param[in] numberOfGlyphs The number of glyphs. * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing). - * @param[in] - * @param[in] + * @param[in] visualModel The visual model. + * @param[in] fontClient The font client. */ void GetGlyphsMetrics( GlyphIndex glyphIndex, Length numberOfGlyphs, @@ -128,15 +128,16 @@ EventData::EventData( DecoratorPtr decorator ) mDecoratorUpdated( false ), mCursorBlinkEnabled( true ), mGrabHandleEnabled( true ), - mGrabHandlePopupEnabled( false ), - mSelectionEnabled( false ), + mGrabHandlePopupEnabled( true ), + mSelectionEnabled( true ), mHorizontalScrollingEnabled( true ), mVerticalScrollingEnabled( false ), mUpdateCursorPosition( false ), mUpdateLeftSelectionPosition( false ), mUpdateRightSelectionPosition( false ), - mScrollAfterUpdateCursorPosition( false ), - mScrollAfterDelete( false ) + mScrollAfterUpdatePosition( false ), + mScrollAfterDelete( false ), + mAllTextSelected( false ) {} EventData::~EventData() @@ -160,28 +161,43 @@ bool Controller::Impl::ProcessInputEvents() { switch( iter->type ) { - case Event::CURSOR_KEY_EVENT: - { - OnCursorKeyEvent( *iter ); - break; - } - case Event::TAP_EVENT: - { - OnTapEvent( *iter ); - break; - } - case Event::PAN_EVENT: - { - OnPanEvent( *iter ); - break; - } - case Event::GRAB_HANDLE_EVENT: - case Event::LEFT_SELECTION_HANDLE_EVENT: - case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through - { - OnHandleEvent( *iter ); - break; - } + case Event::CURSOR_KEY_EVENT: + { + OnCursorKeyEvent( *iter ); + break; + } + case Event::TAP_EVENT: + { + OnTapEvent( *iter ); + break; + } + case Event::LONG_PRESS_EVENT: + { + OnLongPressEvent( *iter ); + break; + } + case Event::PAN_EVENT: + { + OnPanEvent( *iter ); + break; + } + case Event::GRAB_HANDLE_EVENT: + case Event::LEFT_SELECTION_HANDLE_EVENT: + case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through + { + OnHandleEvent( *iter ); + break; + } + case Event::SELECT: + { + OnSelectEvent( *iter ); + break; + } + case Event::SELECT_ALL: + { + OnSelectAllEvent(); + break; + } } } } @@ -193,10 +209,12 @@ 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; @@ -208,39 +226,58 @@ bool Controller::Impl::ProcessInputEvents() mEventData->mDecoratorUpdated = true; mEventData->mScrollAfterDelete = false; } - else if( mEventData->mUpdateLeftSelectionPosition ) + else { - UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); + bool leftScroll = false; + bool rightScroll = false; - if( mEventData->mScrollAfterUpdateCursorPosition ) + if( mEventData->mUpdateLeftSelectionPosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; - } + UpdateSelectionHandle( LEFT_SELECTION_HANDLE ); - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateLeftSelectionPosition = false; - } - else if( mEventData->mUpdateRightSelectionPosition ) - { - UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + if( mEventData->mScrollAfterUpdatePosition ) + { + const Vector2& leftHandlePosition = mEventData->mDecorator->GetPosition( LEFT_SELECTION_HANDLE ); - if( mEventData->mScrollAfterUpdateCursorPosition ) + ScrollToMakePositionVisible( leftHandlePosition ); + leftScroll = true; + } + + SetPopupButtons(); + mEventData->mDecoratorUpdated = true; + mEventData->mUpdateLeftSelectionPosition = false; + } + + if( mEventData->mUpdateRightSelectionPosition ) { - ScrollToMakeCursorVisible(); - mEventData->mScrollAfterUpdateCursorPosition = false; + UpdateSelectionHandle( RIGHT_SELECTION_HANDLE ); + + if( mEventData->mScrollAfterUpdatePosition ) + { + const Vector2& rightHandlePosition = mEventData->mDecorator->GetPosition( RIGHT_SELECTION_HANDLE ); + + ScrollToMakePositionVisible( rightHandlePosition ); + rightScroll = true; + } + + SetPopupButtons(); + mEventData->mDecoratorUpdated = true; + mEventData->mUpdateRightSelectionPosition = false; } - mEventData->mDecoratorUpdated = true; - mEventData->mUpdateRightSelectionPosition = false; + if( leftScroll || rightScroll ) + { + mEventData->mScrollAfterUpdatePosition = false; + } } mEventData->mEventQueue.clear(); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" ); - bool decoratorUpdated = mEventData->mDecoratorUpdated; + const bool decoratorUpdated = mEventData->mDecoratorUpdated; mEventData->mDecoratorUpdated = false; + return decoratorUpdated; } @@ -359,8 +396,7 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) // There is no right to left characters. Clear the directions vector. mLogicalModel->mCharacterDirections.Clear(); } - - } + } Vector& glyphs = mVisualModel->mGlyphs; Vector& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters; @@ -404,6 +440,25 @@ void Controller::Impl::GetDefaultFonts( Vector& fonts, Length numberOfC } } +float Controller::Impl::GetDefaultFontLineHeight() +{ + 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 ); + + return( fontMetrics.ascender - fontMetrics.descender ); +} + void Controller::Impl::OnCursorKeyEvent( const Event& event ) { if( NULL == mEventData ) @@ -423,7 +478,7 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode ) { - if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition ) + if( mLogicalModel->mText.Count() > mEventData->mPrimaryCursorPosition ) { mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition ); } @@ -438,7 +493,7 @@ void Controller::Impl::OnCursorKeyEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; - mEventData->mScrollAfterUpdateCursorPosition = true; + mEventData->mScrollAfterUpdatePosition = true; } void Controller::Impl::OnTapEvent( const Event& event ) @@ -463,12 +518,7 @@ void Controller::Impl::OnTapEvent( const Event& event ) } mEventData->mUpdateCursorPosition = true; - mEventData->mScrollAfterUpdateCursorPosition = true; - } - else if( mEventData->mSelectionEnabled && - ( 2u == tapCount ) ) - { - RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat ); + mEventData->mScrollAfterUpdatePosition = true; } } } @@ -512,6 +562,15 @@ void Controller::Impl::OnPanEvent( const Event& event ) } } +void Controller::Impl::OnLongPressEvent( const Event& event ) +{ + if ( EventData::EDITING == mEventData->mState ) + { + ChangeState ( EventData::EDITING_WITH_POPUP ); + mEventData->mDecoratorUpdated = true; + } +} + void Controller::Impl::OnHandleEvent( const Event& event ) { if( NULL == mEventData ) @@ -521,6 +580,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) } const unsigned int state = event.p1.mUint; + const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state ); if( HANDLE_PRESSED == state ) { @@ -532,7 +592,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( Event::GRAB_HANDLE_EVENT == event.type ) { - ChangeState ( EventData::EDITING ); + ChangeState ( EventData::GRAB_HANDLE_PANNING ); if( handleNewPosition != mEventData->mPrimaryCursorPosition ) { @@ -542,56 +602,444 @@ void Controller::Impl::OnHandleEvent( const Event& event ) } else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type ) { - if( handleNewPosition != mEventData->mLeftSelectionPosition ) + ChangeState ( EventData::SELECTION_HANDLE_PANNING ); + + if( ( handleNewPosition != mEventData->mLeftSelectionPosition ) && + ( handleNewPosition != mEventData->mRightSelectionPosition ) ) { mEventData->mLeftSelectionPosition = handleNewPosition; + + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + mEventData->mUpdateLeftSelectionPosition = true; } } else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) { - if( handleNewPosition != mEventData->mRightSelectionPosition ) + ChangeState ( EventData::SELECTION_HANDLE_PANNING ); + + if( ( handleNewPosition != mEventData->mRightSelectionPosition ) && + ( handleNewPosition != mEventData->mLeftSelectionPosition ) ) { mEventData->mRightSelectionPosition = handleNewPosition; + + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + mEventData->mUpdateRightSelectionPosition = true; } } - } + } // end ( HANDLE_PRESSED == state ) else if( ( HANDLE_RELEASED == state ) || - ( HANDLE_STOP_SCROLLING == state ) ) + handleStopScrolling ) { - if( mEventData->mGrabHandlePopupEnabled ) + CharacterIndex handlePosition = 0u; + if( handleStopScrolling ) { - ChangeState( EventData::EDITING_WITH_POPUP ); + // 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; + + handlePosition = GetClosestCursorIndex( xPosition, yPosition ); } + if( Event::GRAB_HANDLE_EVENT == event.type ) { mEventData->mUpdateCursorPosition = true; - if( HANDLE_STOP_SCROLLING == state ) + ChangeState( EventData::EDITING_WITH_POPUP ); + + 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->mScrollAfterUpdatePosition = mEventData->mPrimaryCursorPosition != handlePosition; + mEventData->mPrimaryCursorPosition = handlePosition; + } + } + else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::SELECTING ); + + if( handleStopScrolling ) + { + mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ); + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition; - mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition ); + if( mEventData->mUpdateLeftSelectionPosition ) + { + mEventData->mLeftSelectionPosition = handlePosition; - mEventData->mScrollAfterUpdateCursorPosition = true; + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + } } } + else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::SELECTING ); + + if( handleStopScrolling ) + { + mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ); + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition; + if( mEventData->mUpdateRightSelectionPosition ) + { + mEventData->mRightSelectionPosition = handlePosition; + 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(); + const Vector2 currentScrollPosition = mEventData->mScrollPosition; mEventData->mScrollPosition.x += xSpeed; ClampHorizontalScroll( actualSize ); - mEventData->mDecoratorUpdated = true; + bool endOfScroll = false; + if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) ) + { + // Notify the decorator there is no more text to scroll. + // The decorator won't send more scroll events. + mEventData->mDecorator->NotifyEndOfScroll(); + // Still need to set the position of the handle. + endOfScroll = true; + } + + // Set the position of the handle. + const bool scrollRightDirection = xSpeed > 0.f; + 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 ); + + Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE ); + + // Position the grag handle close to either the left or right edge. + position.x = scrollRightDirection ? 0.f : mControlSize.width; + + // Get the new handle position. + // The grab 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 ); + + mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition; + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition; + mEventData->mPrimaryCursorPosition = handlePosition; + } + 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 ); + + Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE ); + + // Position the selection handle close to either the left or right edge. + position.x = scrollRightDirection ? 0.f : mControlSize.width; + + // 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 ) + { + const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition ); + mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles; + if( differentHandles ) + { + mEventData->mLeftSelectionPosition = handlePosition; + } + } + else + { + const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition ); + mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles; + if( differentHandles ) + { + mEventData->mRightSelectionPosition = handlePosition; + } + } + + if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) + { + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); + + mEventData->mScrollAfterUpdatePosition = true; + } + } + mEventData->mDecoratorUpdated = true; + } // end ( HANDLE_SCROLLING == state ) +} + +void Controller::Impl::OnSelectEvent( const Event& event ) +{ + if( NULL == mEventData ) + { + // Nothing to do if there is no text. + return; } + + if( mEventData->mSelectionEnabled ) + { + // 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; + + const CharacterIndex leftPosition = mEventData->mLeftSelectionPosition; + const CharacterIndex rightPosition = mEventData->mRightSelectionPosition; + + RepositionSelectionHandles( xPosition, + yPosition ); + + mEventData->mUpdateLeftSelectionPosition = leftPosition != mEventData->mLeftSelectionPosition; + mEventData->mUpdateRightSelectionPosition = rightPosition != mEventData->mRightSelectionPosition; + + mEventData->mScrollAfterUpdatePosition = ( ( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) && + ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ) ); + } +} + +void Controller::Impl::OnSelectAllEvent() +{ + if( NULL == mEventData ) + { + // Nothing to do if there is no text. + return; + } + + if( mEventData->mSelectionEnabled ) + { + RepositionSelectionHandles( 0u, + mLogicalModel->mText.Count() ); + + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; + } +} + +void Controller::Impl::RetrieveSelection( std::string& selectedText, bool deleteAfterRetreival ) +{ + if( mEventData->mLeftSelectionPosition == mEventData->mRightSelectionPosition ) + { + // Nothing to select if handles are in the same place. + selectedText=""; + return; + } + + //Get start and end position of selection + uint32_t startOfSelectedText = mEventData->mLeftSelectionPosition; + uint32_t lengthOfSelectedText = mEventData->mRightSelectionPosition - startOfSelectedText; + + // Validate the start and end selection points + if( ( startOfSelectedText >= 0 ) && ( ( startOfSelectedText + lengthOfSelectedText ) <= mLogicalModel->mText.Count() ) ) + { + //Get text as a UTF8 string + Vector& utf32Characters = mLogicalModel->mText; + + Utf32ToUtf8( &utf32Characters[startOfSelectedText], lengthOfSelectedText, selectedText ); + + if ( deleteAfterRetreival ) // Only delete text if copied successfully + { + // Delete text between handles + Vector& currentText = mLogicalModel->mText; + + Vector::Iterator first = currentText.Begin() + startOfSelectedText; + Vector::Iterator last = first + lengthOfSelectedText; + currentText.Erase( first, last ); + } + mEventData->mPrimaryCursorPosition = mEventData->mLeftSelectionPosition; + mEventData->mScrollAfterDelete = true; + mEventData->mDecoratorUpdated = true; + } +} + +void Controller::Impl::ShowClipboard() +{ + if ( mClipboard ) + { + mClipboard.ShowClipboard(); + } +} + +void Controller::Impl::HideClipboard() +{ + if ( mClipboard ) + { + mClipboard.HideClipboard(); + } +} + +bool Controller::Impl::CopyStringToClipboard( std::string& source ) +{ + //Send string to clipboard + return ( mClipboard && mClipboard.SetItem( source ) ); +} + +void Controller::Impl::SendSelectionToClipboard( bool deleteAfterSending ) +{ + std::string selectedText; + RetrieveSelection( selectedText, deleteAfterSending ); + CopyStringToClipboard( selectedText ); + ChangeState( EventData::EDITING ); +} + +void Controller::Impl::GetTextFromClipboard( unsigned int itemIndex, std::string& retreivedString ) +{ + if ( mClipboard ) + { + retreivedString = mClipboard.GetItem( itemIndex ); + } +} + +void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart, CharacterIndex selectionEnd ) +{ + if( selectionStart == selectionEnd ) + { + // Nothing to select if handles are in the same place. + return; + } + + mEventData->mDecorator->ClearHighlights(); + + mEventData->mLeftSelectionPosition = selectionStart; + mEventData->mRightSelectionPosition = selectionEnd; + + 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(); + const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin(); + const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL; + + // TODO: Better algorithm to create the highlight box. + // TODO: Multi-line. + + // Get the height of the line. + const Vector& lines = mVisualModel->mLines; + const LineRun& firstLine = *lines.Begin(); + const float height = firstLine.ascender + -firstLine.descender; + + // Swap the indices if the start is greater than the end. + const bool indicesSwapped = ( selectionStart > selectionEnd ); + if( indicesSwapped ) + { + std::swap( selectionStart, selectionEnd ); + } + + // Get the indices to the first and last selected glyphs. + const CharacterIndex selectionEndMinusOne = selectionEnd - 1u; + const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); + const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne ); + const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u ); + + // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart ); + bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) ); + + // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd ); + bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) ); + + // Tell the decorator to swap the selection handles if needed. + mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped ); + + const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; + + // Traverse the glyphs. + for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index ) + { + const GlyphInfo& glyph = *( glyphsBuffer + index ); + const Vector2& position = *( positionsBuffer + index ); + + if( splitStartGlyph ) + { + // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersStart ); + const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart ); + } + + // The end point could be in the middle of the ligature. + // Calculate the number of characters selected. + const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex ); + + const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex ); + + mEventData->mDecorator->AddHighlight( xPosition, + offset.y, + xPosition + static_cast( numberOfCharacters ) * glyphAdvance, + height ); + + splitStartGlyph = false; + continue; + } + + if( splitEndGlyph && ( index == glyphEnd ) ) + { + // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersEnd ); + const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd ); + } + + const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex; + + const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast( numberOfCharacters ) ) : 0.f ); + mEventData->mDecorator->AddHighlight( xPosition, + offset.y, + xPosition + static_cast( interGlyphIndex ) * glyphAdvance, + height ); + + splitEndGlyph = false; + continue; + } + + 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 ) @@ -602,32 +1050,79 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY return; } - // TODO - Find which word was selected + if( IsShowingPlaceholderText() ) + { + // Nothing to do if there is the place-holder text. + return; + } - const Vector& glyphs = mVisualModel->mGlyphs; - const Vector::SizeType glyphCount = glyphs.Count(); + 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; + } - const Vector& positions = mVisualModel->mGlyphPositions; - const Vector::SizeType positionCount = positions.Count(); + // 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 ); +} - // Guard against glyphs which did not fit inside the layout - const Vector::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount; +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 + */ - if( count ) + TextSelectionPopup::Buttons buttonsToShow = TextSelectionPopup::NONE; + + if ( ( EventData::SELECTING == mEventData->mState ) || ( EventData::SELECTION_CHANGED == mEventData->mState ) ) { - float primaryX = positions[0].x + mEventData->mScrollPosition.x; - float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x; + buttonsToShow = TextSelectionPopup::Buttons( TextSelectionPopup::CUT | TextSelectionPopup::COPY ); - // TODO - multi-line selection - const Vector& lines = mVisualModel->mLines; - float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f; + if ( !IsClipboardEmpty() ) + { + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::PASTE ) ); + buttonsToShow = TextSelectionPopup::Buttons ( ( buttonsToShow | TextSelectionPopup::CLIPBOARD ) ); + } - mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height ); - mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height ); + 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 ); + } - mEventData->mDecorator->ClearHighlights(); - mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y ); + 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 ) @@ -651,6 +1146,16 @@ void Controller::Impl::ChangeState( EventData::State newState ) 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 ) { @@ -659,6 +1164,21 @@ void Controller::Impl::ChangeState( EventData::State newState ) 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 ) @@ -672,7 +1192,12 @@ void Controller::Impl::ChangeState( EventData::State newState ) 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 ) { @@ -681,16 +1206,68 @@ void Controller::Impl::ChangeState( EventData::State newState ) { mEventData->mDecorator->StartCursorBlink(); } - mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false ); if( mEventData->mSelectionEnabled ) { - mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true ); - mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true ); + 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; } } @@ -714,9 +1291,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 ) { @@ -753,6 +1370,7 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, // 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; @@ -771,9 +1389,11 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, // The character in logical order. const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex; + // Get the script of the character. + const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex ); + // The first glyph for that character in logical order. const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex ); - // The number of glyphs for that character const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); @@ -787,11 +1407,25 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex ); - const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance; + // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»... + const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u; + const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfCharactersInLigature ); + + for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index ) + { + // Find the mid-point of the area containing the glyph + const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast( index ) + 0.5f ) * glyphAdvance; + + if( visualX < glyphCenter ) + { + visualIndex += index; + matched = true; + break; + } + } - if( visualX < glyphX ) + if( matched ) { - matched = true; break; } } @@ -803,7 +1437,10 @@ 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, @@ -813,60 +1450,41 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, // Check if the logical position is the first or the last one of the text. const bool isFirstPosition = 0u == logical; - const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical; + const bool isLastPosition = mLogicalModel->mText.Count() == logical; if( isFirstPosition && isLastPosition ) { - // There is zero characters. Get the default font. - - 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 ); - - cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender; + // There is zero characters. Get the default font's line height. + cursorInfo.lineHeight = GetDefaultFontLineHeight(); cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; - cursorInfo.primaryPosition.x = 0.f; + cursorInfo.primaryPosition.x = 1.f; cursorInfo.primaryPosition.y = 0.f; // Nothing else to do. return; } - // Get the previous logical index. - const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u; - - // Decrease the logical index if it's the last one. - if( isLastPosition ) - { - --logical; - } + // 'logical' is the logical 'cursor' index. + // Get the next and current logical 'character' index. + const CharacterIndex nextCharacterIndex = logical; + const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u; - // Get the direction of the character and the previous one. + // Get the direction of the character and the next one. const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL; CharacterDirection isCurrentRightToLeft = false; - CharacterDirection isPreviousRightToLeft = false; + CharacterDirection isNextRightToLeft = false; if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. { - isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical ); - isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical ); + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex ); + isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex ); } // Get the line where the character is laid-out. - const LineRun* modelLines = mVisualModel->mLines.Begin(); + const LineRun* const modelLines = mVisualModel->mLines.Begin(); - const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical ); + const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex ); const LineRun& line = *( modelLines + lineIndex ); // Get the paragraph's direction. @@ -874,123 +1492,149 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, // Check whether there is an alternative position: - cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) || - ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); + cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) || + ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); // Set the line height. cursorInfo.lineHeight = line.ascender + -line.descender; - // Convert the cursor position into the glyph position. - CharacterIndex characterIndex = logical; - if( cursorInfo.isSecondaryCursor && - ( isRightToLeftParagraph != isCurrentRightToLeft ) ) + // Calculate the primary cursor. + + CharacterIndex index = characterIndex; + if( cursorInfo.isSecondaryCursor ) { - characterIndex = previousLogical; + // If there is a secondary position, the primary cursor may be in a different place than the logical index. + + if( isLastPosition ) + { + // The position of the cursor after the last character needs special + // care depending on its direction and the direction of the paragraph. + + // Need to find the first character after the last character with the paragraph's direction. + // i.e l0 l1 l2 r0 r1 should find r0. + + // TODO: check for more than one line! + index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; + index = mLogicalModel->GetLogicalCharacterIndex( index ); + } + else + { + index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex; + } } - const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); - const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex ); + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); + const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin(); + const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin(); + + // Convert the cursor position into the glyph position. + const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index ); + const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index ); + const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex ); // Get the metrics for the group of glyphs. GlyphMetrics glyphMetrics; - GetGlyphsMetrics( currentGlyphIndex, - numberOfGlyphs, + GetGlyphsMetrics( primaryGlyphIndex, + primaryNumberOfGlyphs, glyphMetrics, mVisualModel, mFontClient ); - float interGlyphAdvance = 0.f; + // Whether to add the glyph's advance to the cursor position. + // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added, + // if the logical cursor is one, the position is the position of the first glyph and the advance is added. + // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic. + // + // FLCP A + // ------ + // 0000 1 + // 0001 1 + // 0010 0 + // 0011 0 + // 0100 1 + // 0101 0 + // 0110 1 + // 0111 0 + // 1000 0 + // 1001 x + // 1010 x + // 1011 1 + // 1100 x + // 1101 x + // 1110 x + // 1111 x + // + // Where F -> isFirstPosition + // L -> isLastPosition + // C -> isCurrentRightToLeft + // P -> isRightToLeftParagraph + // A -> Whether to add the glyph's advance. + + const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) || + ( isFirstPosition && isRightToLeftParagraph ) || + ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) ); + + float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f; + if( !isLastPosition && - ( numberOfCharacters > 1u ) ) + ( primaryNumberOfCharacters > 1u ) ) { - const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex ); - interGlyphAdvance = static_cast( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast( numberOfCharacters ); + const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex ); + + bool isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index ); + } + + Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex; + if( isCurrentRightToLeft ) + { + numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; + } + + glyphAdvance = static_cast( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( primaryNumberOfCharacters ); } // Get the glyph position and x bearing. - const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex ); + const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex ); - // Set the cursor's height. - cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight; + // Set the primary cursor's height. + cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight; - // Set the position. - cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance ); + // Set the primary cursor's position. + cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance; cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; - if( isLastPosition ) + // Calculate the secondary cursor. + + if( cursorInfo.isSecondaryCursor ) { - // The position of the cursor after the last character needs special - // care depending on its direction and the direction of the paragraph. + // Set the secondary cursor's height. + cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; - if( cursorInfo.isSecondaryCursor ) + CharacterIndex index = characterIndex; + if( !isLastPosition ) { - // Need to find the first character after the last character with the paragraph's direction. - // i.e l0 l1 l2 r0 r1 should find r0. - - // TODO: check for more than one line! - characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; - characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex ); - - const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); - - const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex ); - - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( glyphIndex, - numberOfGlyphs, - glyphMetrics, - mVisualModel, - mFontClient ); - - cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance ); - - cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; + index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex; } - else - { - if( !isCurrentRightToLeft ) - { - cursorInfo.primaryPosition.x += glyphMetrics.advance; - } - else - { - cursorInfo.primaryPosition.x -= glyphMetrics.advance; - } - } - } - // Set the alternative cursor position. - if( cursorInfo.isSecondaryCursor ) - { - // Convert the cursor position into the glyph position. - const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical ); - const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex ); + const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index ); + const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index ); - // Get the glyph position. - const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex ); + const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex ); - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( previousGlyphIndex, - numberOfGlyphs, + GetGlyphsMetrics( secondaryGlyphIndex, + secondaryNumberOfGlyphs, glyphMetrics, mVisualModel, mFontClient ); - // Set the cursor position and height. - cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) || - ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f ); - - cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; - + // Set the secondary cursor's position. + cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance ); cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender ); - - // Update the primary cursor height as well. - cursorInfo.primaryCursorHeight *= 0.5f; } } @@ -1004,25 +1648,27 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition; - const Script script = mLogicalModel->GetScript( index ); - const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); - const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + + GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); + Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); - Length numberOfCharacters = 0u; - if( TextAbstraction::LATIN == script ) + if( numberOfCharacters > 1u ) { - // Prevents to jump the whole Latin ligatures like fi, ff, ... - numberOfCharacters = 1u; + const Script script = mLogicalModel->GetScript( index ); + if( HasLigatureMustBreak( script ) ) + { + // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ... + numberOfCharacters = 1u; + } } else { - GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); - numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); - while( 0u == numberOfCharacters ) { - numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); ++glyphIndex; + numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); } } @@ -1185,6 +1831,11 @@ void Controller::Impl::UpdateSelectionHandle( HandleType handleType ) cursorPosition.x, cursorPosition.y, cursorInfo.lineHeight ); + + // If selection handle at start of the text and other at end of the text then all text is selected. + const CharacterIndex startOfSelection = std::min( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); + const CharacterIndex endOfSelection = std::max ( mEventData->mLeftSelectionPosition, mEventData->mRightSelectionPosition ); + mEventData->mAllTextSelected = ( startOfSelection == 0 ) && ( endOfSelection == mLogicalModel->mText.Count() ); } void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize ) @@ -1221,27 +1872,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; } @@ -1268,41 +1911,51 @@ void Controller::Impl::ScrollTextToMatchCursor() 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 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, + // Sets the grab handle position. + mEventData->mDecorator->SetPosition( GRAB_HANDLE, + cursorPosition.x, + cursorPosition.y, + cursorInfo.lineHeight ); + + if( cursorInfo.isSecondaryCursor ) + { + 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 ); + } + // Set which cursors are active according the state. + if( ( EventData::EDITING == mEventData->mState ) || + ( EventData::EDITING_WITH_POPUP == mEventData->mState ) || + ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) + { 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 + { + mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE ); + } } void Controller::Impl::RequestRelayout()