Dali-Text: Keyboard Shortcuts
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / cursor-helper-functions.cpp
index 1fbca0d..19db7ae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -43,7 +43,7 @@ struct FindWordData
   : textBuffer( textBuffer ),
     totalNumberOfCharacters( totalNumberOfCharacters ),
     hitCharacter( hitCharacter ),
-    foundIndex( 0u ),
+    foundIndex( 0 ),
     isWhiteSpace( isWhiteSpace ),
     isNewParagraph( isNewParagraph )
   {}
@@ -136,10 +136,17 @@ namespace Text
 {
 
 LineIndex GetClosestLine( VisualModelPtr visualModel,
-                          float visualY )
+                          float visualY,
+                          bool& matchedLine )
 {
   float totalHeight = 0.f;
-  LineIndex lineIndex = 0u;
+  LineIndex lineIndex = 0;
+  matchedLine = false;
+
+  if( visualY < 0.f )
+  {
+    return 0;
+  }
 
   const Vector<LineRun>& lines = visualModel->mLines;
 
@@ -156,16 +163,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
 
     if( visualY < totalHeight )
     {
+      matchedLine = true;
       return lineIndex;
     }
   }
 
-  if( lineIndex == 0u )
+  if( lineIndex == 0 )
   {
     return 0;
   }
 
-  return lineIndex-1;
+  return lineIndex - 1u;
 }
 
 float CalculateLineOffset( const Vector<LineRun>& lines,
@@ -192,11 +200,16 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
                                       LogicalModelPtr logicalModel,
                                       MetricsPtr metrics,
                                       float visualX,
-                                      float visualY )
+                                      float visualY,
+                                      CharacterHitTest::Mode mode,
+                                      bool& matchedCharacter )
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY );
 
-  CharacterIndex logicalIndex = 0u;
+  // Whether there is a hit on a glyph.
+  matchedCharacter = false;
+
+  CharacterIndex logicalIndex = 0;
 
   const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
   const Length totalNumberOfLines  = visualModel->mLines.Count();
@@ -206,9 +219,19 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     return logicalIndex;
   }
 
+  // Whether there is a hit on a line.
+  bool matchedLine = false;
+
   // Find which line is closest.
   const LineIndex lineIndex = Text::GetClosestLine( visualModel,
-                                                    visualY );
+                                                    visualY,
+                                                    matchedLine );
+
+  if( !matchedLine && ( CharacterHitTest::TAP == mode ) )
+  {
+    // Return the first or the last character if the touch point doesn't hit a line.
+    return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count();
+  }
 
   // Convert from text's coords to line's coords.
   const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
@@ -241,12 +264,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
   // The character's direction buffer.
   const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
 
-  // Whether there is a hit on a glyph.
-  bool matched = false;
+  // Whether the touch point if before the first glyph.
+  bool isBeforeFirstGlyph = false;
 
   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
   CharacterIndex visualIndex = startCharacter;
-  Length numberOfVisualCharacters = 0u;
+  Length numberOfVisualCharacters = 0;
   for( ; visualIndex < endCharacter; ++visualIndex )
   {
     // The character in logical order.
@@ -257,7 +280,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
     ++numberOfVisualCharacters;
 
-    if( 0u != numberOfGlyphs )
+    if( 0 != numberOfGlyphs )
     {
       // Get the first character/glyph of the group of glyphs.
       const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
@@ -275,6 +298,17 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       // Get the position of the first glyph.
       const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
 
+      if( startCharacter == visualIndex )
+      {
+        const float glyphPosition = -glyphMetrics.xBearing + position.x;
+
+        if( visualX < glyphPosition )
+        {
+          isBeforeFirstGlyph = true;
+          break;
+        }
+      }
+
       // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا).
       Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
       if( direction != LTR )
@@ -284,7 +318,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
         // number of glyphs in the table is found first.
         // Jump the number of characters to the next glyph is needed.
 
-        if( 0u == numberOfCharacters )
+        if( 0 == numberOfCharacters )
         {
           // TODO: This is a workaround to fix an issue with complex characters in the arabic
           // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة
@@ -298,7 +332,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
           // Find the number of characters.
           for( GlyphIndex index = firstLogicalGlyphIndex + 1u;
-               ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ;
+               ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs );
                ++index )
           {
             numberOfCharacters = *( charactersPerGlyphBuffer + index );
@@ -322,7 +356,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
       const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
 
-      CharacterIndex index = 0u;
+      CharacterIndex index = 0;
       for( ; index < numberOfBlocks; ++index )
       {
         // Find the mid-point of the area containing the glyph
@@ -330,31 +364,39 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
         if( visualX < glyphCenter )
         {
-          matched = true;
+          matchedCharacter = true;
           break;
         }
       }
 
-      if( matched )
+      if( matchedCharacter )
       {
         // If the glyph is shaped from more than one character, it matches the character of the glyph.
         visualIndex = firstVisualCharacterIndex + index;
         break;
       }
 
-      numberOfVisualCharacters = 0u;
+      numberOfVisualCharacters = 0;
     }
-  }
+  } // for characters in visual order.
 
   // The number of characters of the whole text.
   const Length totalNumberOfCharacters = logicalModel->mText.Count();
 
   // Return the logical position of the cursor in characters.
 
-  if( !matched )
+  if( !matchedCharacter )
   {
-    // If no character is matched, then the last character (in visual order) of the line is used.
-    visualIndex = endCharacter;
+    if( isBeforeFirstGlyph )
+    {
+      // If no character is matched, then the first character (in visual order) of the line is used.
+      visualIndex = startCharacter;
+    }
+    else
+    {
+      // If no character is matched, then the last character (in visual order) of the line is used.
+      visualIndex = endCharacter;
+    }
   }
 
   // Get the paragraph direction.
@@ -422,24 +464,26 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 }
 
 
-void GetCursorPosition( VisualModelPtr visualModel,
-                        LogicalModelPtr logicalModel,
-                        MetricsPtr metrics,
-                        CharacterIndex logical,
+void GetCursorPosition( GetCursorPositionParameters& parameters,
                         CursorInfo& cursorInfo )
 {
+  const LineRun* const modelLines = parameters.visualModel->mLines.Begin();
+  if( NULL == modelLines )
+  {
+    // Nothing to do.
+    return;
+  }
+
   // Whether the logical cursor position is at the end of the whole text.
-  const bool isLastPosition = logicalModel->mText.Count() == logical;
+  const bool isLastPosition = parameters.logicalModel->mText.Count() == parameters.logical;
 
   // Get the line where the character is laid-out.
-  const CharacterIndex characterOfLine = isLastPosition ? ( logical - 1u ) : logical;
+  const CharacterIndex characterOfLine = isLastPosition ? ( parameters.logical - 1u ) : parameters.logical;
 
   // Whether the cursor is in the last position and the last position is a new paragraph character.
-  const bool isLastNewParagraph = isLastPosition && TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + characterOfLine ) );
-
-  const LineRun* const modelLines = visualModel->mLines.Begin();
+  const bool isLastNewParagraph = parameters.isMultiline && isLastPosition && TextAbstraction::IsNewParagraph( *( parameters.logicalModel->mText.Begin() + characterOfLine ) );
 
-  const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterOfLine );
+  const LineIndex lineIndex = parameters.visualModel->GetLineOfCharacter( characterOfLine );
   const LineRun& line = *( modelLines + lineIndex );
 
   if( isLastNewParagraph )
@@ -451,7 +495,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
     cursorInfo.isSecondaryCursor = false;
 
     // Set the line offset and height.
-    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+    cursorInfo.lineOffset = CalculateLineOffset( parameters.visualModel->mLines,
                                                  newLineIndex );
 
     // The line height is the addition of the line ascender and the line descender.
@@ -462,28 +506,25 @@ void GetCursorPosition( VisualModelPtr visualModel,
     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
 
     // Set the primary cursor's position.
-    cursorInfo.primaryPosition.x = 0.f;
+    cursorInfo.primaryPosition.x = ( LTR == line.direction ) ? newLine.alignmentOffset : parameters.visualModel->mControlSize.width - newLine.alignmentOffset;
     cursorInfo.primaryPosition.y = cursorInfo.lineOffset;
-
-    // Transform the cursor info from line's coords to text's coords.
-    cursorInfo.primaryPosition.x += ( LTR == line.direction ) ? 0.f : visualModel->mControlSize.width;
   }
   else
   {
     // Whether this line is a bidirectional line.
-    const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( characterOfLine );
+    const bool bidiLineFetched = parameters.logicalModel->FetchBidirectionalLineInfo( characterOfLine );
 
     // Check if the logical position is the first or the last one of the line.
-    const bool isFirstPositionOfLine = line.characterRun.characterIndex == logical;
-    const bool isLastPositionOfLine = line.characterRun.characterIndex + line.characterRun.numberOfCharacters == logical;
+    const bool isFirstPositionOfLine = line.characterRun.characterIndex == parameters.logical;
+    const bool isLastPositionOfLine = line.characterRun.characterIndex + line.characterRun.numberOfCharacters == parameters.logical;
 
     // 'logical' is the logical 'cursor' index.
     // Get the next and current logical 'character' index.
-    const CharacterIndex characterIndex = isFirstPositionOfLine ? logical : logical - 1u;
-    const CharacterIndex nextCharacterIndex = isLastPositionOfLine ? characterIndex : logical;
+    const CharacterIndex characterIndex = isFirstPositionOfLine ? parameters.logical : parameters.logical - 1u;
+    const CharacterIndex nextCharacterIndex = isLastPositionOfLine ? characterIndex : parameters.logical;
 
     // The character's direction buffer.
-    const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
+    const CharacterDirection* const directionsBuffer = bidiLineFetched ? parameters.logicalModel->mCharacterDirections.Begin() : NULL;
 
     CharacterDirection isCurrentRightToLeft = false;
     CharacterDirection isNextRightToLeft = false;
@@ -502,7 +543,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                                      ( isFirstPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) );
 
     // Set the line offset and height.
-    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+    cursorInfo.lineOffset = CalculateLineOffset( parameters.visualModel->mLines,
                                                  lineIndex );
 
     // The line height is the addition of the line ascender and the line descender.
@@ -527,7 +568,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
         if( bidiLineFetched )
         {
-          index = logicalModel->GetLogicalCharacterIndex( index );
+          index = parameters.logicalModel->GetLogicalCharacterIndex( index );
         }
       }
       else if( isFirstPositionOfLine )
@@ -535,7 +576,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         index = isRightToLeftParagraph ? line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u : line.characterRun.characterIndex;
         if( bidiLineFetched )
         {
-          index = logicalModel->GetLogicalCharacterIndex( index );
+          index = parameters.logicalModel->GetLogicalCharacterIndex( index );
         }
       }
       else
@@ -544,12 +585,12 @@ void GetCursorPosition( VisualModelPtr visualModel,
       }
     }
 
-    const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
-    const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
-    const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
-    const CharacterIndex* const glyphsToCharactersBuffer = visualModel->mGlyphsToCharacters.Begin();
-    const Vector2* const glyphPositionsBuffer = visualModel->mGlyphPositions.Begin();
-    const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin();
+    const GlyphIndex* const charactersToGlyphBuffer = parameters.visualModel->mCharactersToGlyph.Begin();
+    const Length* const glyphsPerCharacterBuffer = parameters.visualModel->mGlyphsPerCharacter.Begin();
+    const Length* const charactersPerGlyphBuffer = parameters.visualModel->mCharactersPerGlyph.Begin();
+    const CharacterIndex* const glyphsToCharactersBuffer = parameters.visualModel->mGlyphsToCharacters.Begin();
+    const Vector2* const glyphPositionsBuffer = parameters.visualModel->mGlyphPositions.Begin();
+    const GlyphInfo* const glyphInfoBuffer = parameters.visualModel->mGlyphs.Begin();
 
     // Convert the cursor position into the glyph position.
     const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
@@ -562,7 +603,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                       primaryNumberOfGlyphs,
                       glyphMetrics,
                       glyphInfoBuffer,
-                      metrics );
+                      parameters.metrics );
 
     // 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,
@@ -611,7 +652,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         isCurrentRightToLeft = *( directionsBuffer + index );
       }
 
-      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex;
+      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex;
       if( isCurrentRightToLeft )
       {
         numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
@@ -626,9 +667,10 @@ void GetCursorPosition( VisualModelPtr visualModel,
     // Set the primary cursor's height.
     cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
 
+    cursorInfo.glyphOffset = line.ascender - glyphMetrics.ascender;
     // Set the primary cursor's position.
     cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
-    cursorInfo.primaryPosition.y = cursorInfo.lineOffset + line.ascender - glyphMetrics.ascender;
+    cursorInfo.primaryPosition.y = cursorInfo.lineOffset + cursorInfo.glyphOffset;
 
     // Transform the cursor info from line's coords to text's coords.
     cursorInfo.primaryPosition.x += line.alignmentOffset;
@@ -654,7 +696,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                         secondaryNumberOfGlyphs,
                         glyphMetrics,
                         glyphInfoBuffer,
-                        metrics );
+                        parameters.metrics );
 
       // Set the secondary cursor's position.
 
@@ -692,9 +734,9 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
                            float visualX,
                            float visualY,
                            CharacterIndex& startIndex,
-                           CharacterIndex& endIndex )
+                           CharacterIndex& endIndex,
+                           CharacterIndex& noTextHitIndex )
 {
-
 /*
   Hit character                                           Select
 |-------------------------------------------------------|------------------------------------------|
@@ -705,23 +747,33 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
 | On a new paragraph character                          | The word or group of white spaces before |
 |-------------------------------------------------------|------------------------------------------|
 */
+  const Length totalNumberOfCharacters = logicalModel->mText.Count();
+  startIndex = 0;
+  endIndex = 0;
+  noTextHitIndex = 0;
 
+  if( 0 == totalNumberOfCharacters )
+  {
+    // Nothing to do if the model is empty.
+    return false;
+  }
+
+  bool matchedCharacter = false;
   CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel,
                                                              logicalModel,
                                                              metrics,
                                                              visualX,
-                                                             visualY );
+                                                             visualY,
+                                                             CharacterHitTest::TAP,
+                                                             matchedCharacter );
 
-  const Length totalNumberOfCharacters = logicalModel->mText.Count();
-
-  DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
-
-  if( 0u == totalNumberOfCharacters )
+  if( !matchedCharacter )
   {
-    // Nothing to do if the model is empty.
-    return false;
+    noTextHitIndex = hitCharacter;
   }
 
+  DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+
   if( hitCharacter >= totalNumberOfCharacters )
   {
     // Closest hit character is the last character.
@@ -757,7 +809,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
   {
     // Find the first character before the hit one which is not a new paragraph character.
 
-    if( hitCharacter > 0u )
+    if( hitCharacter > 0 )
     {
       endIndex = hitCharacter - 1u;
       for( ; endIndex > 0; --endIndex )
@@ -790,13 +842,13 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
     {
       // Select the word before or after the white space
 
-      if( 0u == hitCharacter )
+      if( 0 == hitCharacter )
       {
         data.isWhiteSpace = false;
         FindEndOfWord( data );
         endIndex = data.foundIndex;
       }
-      else if( hitCharacter > 0u )
+      else if( hitCharacter > 0 )
       {
         // Find the start of the word.
         data.hitCharacter = hitCharacter - 1u;
@@ -809,7 +861,7 @@ bool FindSelectionIndices( VisualModelPtr visualModel,
     }
   }
 
-  return true;
+  return matchedCharacter;
 }
 
 } // namespace Text