From 1bd947edf624ffee8fd4b6ce43c53a9b08513e0f Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Mon, 18 Jul 2016 11:28:46 +0100 Subject: [PATCH] Fix text selection on white spaces or new lines paragraphs. Hit character Select |-------------------------------------------------------|------------------------------------------| | On a word | The word | | On a single white space between words | The word before or after the white space | | On one of the multiple contiguous white spaces | The white spaces | | On a single white space which is in the position zero | The white space and the next word | | On a new paragraph character | The word or group of white spaces before | |-------------------------------------------------------|------------------------------------------| Change-Id: I485221e90e716d4262c7e5fd3c114f0851e7177f Signed-off-by: Victor Cebollada --- .../internal/text/cursor-helper-functions.cpp | 193 +++++++++++++++++++-- .../internal/text/cursor-helper-functions.h | 4 +- .../internal/text/text-controller-impl.cpp | 51 +++--- dali-toolkit/internal/text/text-controller.cpp | 2 - 4 files changed, 211 insertions(+), 39 deletions(-) diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index 10c1e22..9c95650 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -33,6 +33,97 @@ namespace const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction. +struct FindWordData +{ + FindWordData( const Dali::Toolkit::Text::Character* const textBuffer, + Dali::Toolkit::Text::Length totalNumberOfCharacters, + Dali::Toolkit::Text::CharacterIndex hitCharacter, + bool isWhiteSpace, + bool isNewParagraph ) + : textBuffer( textBuffer ), + totalNumberOfCharacters( totalNumberOfCharacters ), + hitCharacter( hitCharacter ), + foundIndex( 0u ), + isWhiteSpace( isWhiteSpace ), + isNewParagraph( isNewParagraph ) + {} + + ~FindWordData() + {} + + const Dali::Toolkit::Text::Character* const textBuffer; + Dali::Toolkit::Text::Length totalNumberOfCharacters; + Dali::Toolkit::Text::CharacterIndex hitCharacter; + Dali::Toolkit::Text::CharacterIndex foundIndex; + bool isWhiteSpace : 1u; + bool isNewParagraph : 1u; +}; + +bool IsWhiteSpaceOrNewParagraph( Dali::Toolkit::Text::Character character, + bool isHitWhiteSpace, + bool isHitWhiteSpaceOrNewParagraph ) +{ + bool isWhiteSpaceOrNewParagraph = false; + if( isHitWhiteSpaceOrNewParagraph ) + { + if( isHitWhiteSpace ) + { + // Whether the current character is a white space. Note a new paragraph character is a white space as well but here is not wanted. + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ) && !Dali::TextAbstraction::IsNewParagraph( character ); + } + else + { + // Whether the current character is a new paragraph character. + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsNewParagraph( character ); + } + } + else + { + // Whether the current character is a white space or a new paragraph character (note the new paragraph character is a white space as well). + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ); + } + + return isWhiteSpaceOrNewParagraph; +} + +void FindStartOfWord( FindWordData& data ) +{ + const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph; + + for( data.foundIndex = data.hitCharacter; data.foundIndex > 0; --data.foundIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex - 1u ); + + const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character, + data.isWhiteSpace, + isHitWhiteSpaceOrNewParagraph ); + + if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph ) + { + break; + } + } +} + +void FindEndOfWord( FindWordData& data ) +{ + const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph; + + for( data.foundIndex = data.hitCharacter + 1u; data.foundIndex < data.totalNumberOfCharacters; ++data.foundIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex ); + + const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character, + data.isWhiteSpace, + isHitWhiteSpaceOrNewParagraph ); + + if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph ) + { + break; + } + } +} + } //namespace namespace Dali @@ -566,7 +657,7 @@ void GetCursorPosition( VisualModelPtr visualModel, } } -void FindSelectionIndices( VisualModelPtr visualModel, +bool FindSelectionIndices( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, @@ -574,52 +665,122 @@ void FindSelectionIndices( VisualModelPtr visualModel, CharacterIndex& startIndex, CharacterIndex& endIndex ) { + +/* + Hit character Select +|-------------------------------------------------------|------------------------------------------| +| On a word | The word | +| On a single white space between words | The word before or after the white space | +| On one of the multiple contiguous white spaces | The white spaces | +| On a single white space which is in the position zero | The white space and the next word | +| On a new paragraph character | The word or group of white spaces before | +|-------------------------------------------------------|------------------------------------------| +*/ + CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel, logicalModel, metrics, visualX, visualY ); - DALI_ASSERT_DEBUG( hitCharacter <= logicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" ); - if( logicalModel->mText.Count() == 0 ) + const Length totalNumberOfCharacters = logicalModel->mText.Count(); + + DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + + if( 0u == totalNumberOfCharacters ) { - return; // if model empty + // Nothing to do if the model is empty. + return false; } - if( hitCharacter >= logicalModel->mText.Count() ) + if( hitCharacter >= totalNumberOfCharacters ) { // Closest hit character is the last character. - if( hitCharacter == logicalModel->mText.Count() ) + if( hitCharacter == totalNumberOfCharacters ) { hitCharacter--; //Hit character index set to last character in logical model } else { // hitCharacter is out of bounds - return; + return false; } } + const Character* const textBuffer = logicalModel->mText.Begin(); + startIndex = hitCharacter; endIndex = hitCharacter; - bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( logicalModel->mText[hitCharacter] ); - // Find the start and end of the text - for( startIndex = hitCharacter; startIndex > 0; --startIndex ) + // Whether the hit character is a new paragraph character. + const bool isHitCharacterNewParagraph = TextAbstraction::IsNewParagraph( *( textBuffer + hitCharacter ) ); + + // Whether the hit character is a white space. Note a new paragraph character is a white space as well but here is not wanted. + const bool isHitCharacterWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + hitCharacter ) ) && !isHitCharacterNewParagraph; + + FindWordData data( textBuffer, + totalNumberOfCharacters, + hitCharacter, + isHitCharacterWhiteSpace, + isHitCharacterNewParagraph ); + + if( isHitCharacterNewParagraph ) { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ startIndex-1 ] ) ) + // Find the first character before the hit one which is not a new paragraph character. + + if( hitCharacter > 0u ) { - break; + endIndex = hitCharacter - 1u; + for( ; endIndex > 0; --endIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + endIndex ); + + if( !Dali::TextAbstraction::IsNewParagraph( character ) ) + { + break; + } + } } + + data.hitCharacter = endIndex; + data.isNewParagraph = false; + data.isWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + data.hitCharacter ) ); } - const CharacterIndex pastTheEnd = logicalModel->mText.Count(); - for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + + // Find the start of the word. + FindStartOfWord( data ); + startIndex = data.foundIndex; + + // Find the end of the word. + FindEndOfWord( data ); + endIndex = data.foundIndex; + + if( 1u == ( endIndex - startIndex ) ) { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ endIndex ] ) ) + if( isHitCharacterWhiteSpace ) { - break; + // Select the word before or after the white space + + if( 0u == hitCharacter ) + { + data.isWhiteSpace = false; + FindEndOfWord( data ); + endIndex = data.foundIndex; + } + else if( hitCharacter > 0u ) + { + // Find the start of the word. + data.hitCharacter = hitCharacter - 1u; + data.isWhiteSpace = false; + FindStartOfWord( data ); + startIndex = data.foundIndex; + + --endIndex; + } } } + + return true; } } // namespace Text diff --git a/dali-toolkit/internal/text/cursor-helper-functions.h b/dali-toolkit/internal/text/cursor-helper-functions.h index e89c335..5896b4d 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.h +++ b/dali-toolkit/internal/text/cursor-helper-functions.h @@ -128,8 +128,10 @@ void GetCursorPosition( VisualModelPtr visualModel, * @param[in] visualY The touch point 'y' in text's coords. * @param[out] startIndex Index to the first character of the selected word. * @param[out] endIndex Index to the last character of the selected word. + * + * @return @e true if the indices are found. */ -void FindSelectionIndices( VisualModelPtr visualModel, +bool FindSelectionIndices( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index e4029b3..6c7f694 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -1544,13 +1544,6 @@ void Controller::Impl::OnSelectEvent( const Event& event ) // Calculates the logical position from the x,y coords. RepositionSelectionHandles( xPosition, yPosition ); - - mEventData->mUpdateLeftSelectionPosition = true; - mEventData->mUpdateRightSelectionPosition = true; - mEventData->mUpdateHighlightBox = true; - mEventData->mUpdateCursorPosition = false; - - mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); } } @@ -1566,6 +1559,8 @@ void Controller::Impl::OnSelectAllEvent() if( mEventData->mSelectionEnabled ) { + ChangeState( EventData::SELECTING ); + mEventData->mLeftSelectionPosition = 0u; mEventData->mRightSelectionPosition = mLogicalModel->mText.Count(); @@ -2057,24 +2052,40 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY // Find which word was selected CharacterIndex selectionStart( 0 ); CharacterIndex selectionEnd( 0 ); - FindSelectionIndices( mVisualModel, - mLogicalModel, - mMetrics, - visualX, - visualY, - selectionStart, - selectionEnd ); + const bool indicesFound = FindSelectionIndices( mVisualModel, + mLogicalModel, + mMetrics, + visualX, + visualY, + selectionStart, + selectionEnd ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); - if( selectionStart == selectionEnd ) + if( indicesFound ) { - ChangeState( EventData::EDITING ); - // Nothing to select. i.e. a white space, out of bounds - return; + ChangeState( EventData::SELECTING ); + + mEventData->mLeftSelectionPosition = selectionStart; + mEventData->mRightSelectionPosition = selectionEnd; + + mEventData->mUpdateLeftSelectionPosition = true; + mEventData->mUpdateRightSelectionPosition = true; + mEventData->mUpdateHighlightBox = true; + + mEventData->mScrollAfterUpdatePosition = ( mEventData->mLeftSelectionPosition != mEventData->mRightSelectionPosition ); } + else + { + // Nothing to select. i.e. a white space, out of bounds + ChangeState( EventData::EDITING ); - mEventData->mLeftSelectionPosition = selectionStart; - mEventData->mRightSelectionPosition = selectionEnd; + mEventData->mPrimaryCursorPosition = selectionEnd; + + mEventData->mUpdateCursorPosition = true; + mEventData->mUpdateGrabHandlePosition = true; + mEventData->mScrollAfterUpdatePosition = true; + mEventData->mUpdateInputStyle = true; + } } void Controller::Impl::SetPopupButtons() diff --git a/dali-toolkit/internal/text/text-controller.cpp b/dali-toolkit/internal/text/text-controller.cpp index 1cf95fd..261575f 100644 --- a/dali-toolkit/internal/text/text-controller.cpp +++ b/dali-toolkit/internal/text/text-controller.cpp @@ -2494,8 +2494,6 @@ void Controller::SelectEvent( float x, float y, bool selectAll ) if( NULL != mImpl->mEventData ) { - mImpl->ChangeState( EventData::SELECTING ); - if( selectAll ) { Event event( Event::SELECT_ALL ); -- 2.7.4