Fix text selection on white spaces or new lines paragraphs.
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / cursor-helper-functions.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