Fix text selection on white spaces or new lines paragraphs. 56/80556/3
authorVictor Cebollada <v.cebollada@samsung.com>
Mon, 18 Jul 2016 10:28:46 +0000 (11:28 +0100)
committerVíctor Cebollada <v.cebollada@samsung.com>
Tue, 19 Jul 2016 10:20:24 +0000 (03:20 -0700)
  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 <v.cebollada@samsung.com>
dali-toolkit/internal/text/cursor-helper-functions.cpp
dali-toolkit/internal/text/cursor-helper-functions.h
dali-toolkit/internal/text/text-controller-impl.cpp
dali-toolkit/internal/text/text-controller.cpp

index 10c1e22..9c95650 100644 (file)
@@ -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
index e89c335..5896b4d 100644 (file)
@@ -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,
index e4029b3..6c7f694 100644 (file)
@@ -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()
index 1cf95fd..261575f 100644 (file)
@@ -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 );