Ensure text starting with weak character reports correct text direction
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller.cpp
index 7847fb9..c618006 100755 (executable)
@@ -25,6 +25,7 @@
 #include <dali/integration-api/debug.h>
 #include <dali/devel-api/adaptor-framework/clipboard-event-notifier.h>
 #include <dali/devel-api/text-abstraction/font-client.h>
+#include <dali/devel-api/adaptor-framework/key-devel.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/public-api/controls/text-controls/placeholder-properties.h>
@@ -48,6 +49,10 @@ const float MAX_FLOAT = std::numeric_limits<float>::max();
 
 const std::string EMPTY_STRING("");
 
+const std::string KEY_C_NAME = "c";
+const std::string KEY_V_NAME = "v";
+const std::string KEY_X_NAME = "x";
+
 const char * const PLACEHOLDER_TEXT = "text";
 const char * const PLACEHOLDER_TEXT_FOCUSED = "textFocused";
 const char * const PLACEHOLDER_COLOR = "color";
@@ -217,7 +222,7 @@ bool Controller::IsAutoScrollEnabled() const
 
 CharacterDirection Controller::GetAutoScrollDirection() const
 {
-  return mImpl->mAutoScrollDirectionRTL;
+  return mImpl->mIsTextDirectionRTL;
 }
 
 float Controller::GetAutoScrollLineAlignment() const
@@ -346,6 +351,7 @@ void Controller::SetMultiLineEnabled( bool enable )
                                                                           ALIGN              |
                                                                           REORDER );
 
+    mImpl->mTextUpdateInfo.mFullRelayoutNeeded = true;
     mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | layoutOperations );
 
     mImpl->RequestRelayout();
@@ -460,6 +466,16 @@ bool Controller::IsSelectionEnabled() const
   return mImpl->mEventData->mSelectionEnabled;
 }
 
+void Controller::SetShiftSelectionEnabled( bool enabled )
+{
+  mImpl->mEventData->mShiftSelectionFlag = enabled;
+}
+
+bool Controller::IsShiftSelectionEnabled() const
+{
+  return mImpl->mEventData->mShiftSelectionFlag;
+}
+
 // public : Update
 
 void Controller::SetText( const std::string& text )
@@ -538,6 +554,9 @@ void Controller::SetText( const std::string& text )
     // The natural size needs to be re-calculated.
     mImpl->mRecalculateNaturalSize = true;
 
+    // The text direction needs to be updated.
+    mImpl->mUpdateTextDirection = true;
+
     // Apply modifications to the model
     mImpl->mOperationsPending = ALL_OPERATIONS;
   }
@@ -1176,14 +1195,14 @@ const Vector4& Controller::GetOutlineColor() const
   return mImpl->mModel->mVisualModel->GetOutlineColor();
 }
 
-void Controller::SetOutlineWidth( float width )
+void Controller::SetOutlineWidth( unsigned int width )
 {
   mImpl->mModel->mVisualModel->SetOutlineWidth( width );
 
   mImpl->RequestRelayout();
 }
 
-float Controller::GetOutlineWidth() const
+unsigned int Controller::GetOutlineWidth() const
 {
   return mImpl->mModel->mVisualModel->GetOutlineWidth();
 }
@@ -1228,10 +1247,15 @@ const std::string& Controller::GetDefaultOutlineProperties() const
   return EMPTY_STRING;
 }
 
-void Controller::SetDefaultLineSpacing( float lineSpacing )
+bool Controller::SetDefaultLineSpacing( float lineSpacing )
 {
-  //TODO finish implementation
-  mImpl->mLayoutEngine.SetDefaultLineSpacing( lineSpacing );
+  if( std::abs(lineSpacing - mImpl->mLayoutEngine.GetDefaultLineSpacing()) > Math::MACHINE_EPSILON_1000 )
+  {
+    mImpl->mLayoutEngine.SetDefaultLineSpacing(lineSpacing);
+    mImpl->mRecalculateNaturalSize = true;
+    return true;
+  }
+  return false;
 }
 
 float Controller::GetDefaultLineSpacing() const
@@ -2097,6 +2121,53 @@ void Controller::GetPlaceholderProperty( Property::Map& map )
   }
 }
 
+Toolkit::DevelText::TextDirection::Type Controller::GetTextDirection()
+{
+  // Make sure the model is up-to-date before layouting
+  ProcessModifyEvents();
+
+  if ( mImpl->mUpdateTextDirection )
+  {
+    // Operations that can be done only once until the text changes.
+    const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( GET_SCRIPTS       |
+                                                                           VALIDATE_FONTS    |
+                                                                           GET_LINE_BREAKS   |
+                                                                           GET_WORD_BREAKS   |
+                                                                           BIDI_INFO         |
+                                                                           SHAPE_TEXT        );
+
+    // Set the update info to relayout the whole text.
+    mImpl->mTextUpdateInfo.mParagraphCharacterIndex = 0u;
+    mImpl->mTextUpdateInfo.mRequestedNumberOfCharacters = mImpl->mModel->mLogicalModel->mText.Count();
+
+    // Make sure the model is up-to-date before layouting
+    mImpl->UpdateModel( onlyOnceOperations );
+
+    Vector3 naturalSize;
+    DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ),
+                static_cast<OperationsMask>( onlyOnceOperations |
+                                             LAYOUT | REORDER | UPDATE_DIRECTION ),
+                naturalSize.GetVectorXY() );
+
+    // Clear the update info. This info will be set the next time the text is updated.
+    mImpl->mTextUpdateInfo.Clear();
+
+    mImpl->mUpdateTextDirection = false;
+  }
+
+  return mImpl->mIsTextDirectionRTL ? Toolkit::DevelText::TextDirection::RIGHT_TO_LEFT : Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT;
+}
+
+Toolkit::DevelText::VerticalLineAlignment::Type Controller::GetVerticalLineAlignment() const
+{
+  return mImpl->mModel->GetVerticalLineAlignment();
+}
+
+void Controller::SetVerticalLineAlignment( Toolkit::DevelText::VerticalLineAlignment::Type alignment )
+{
+  mImpl->mModel->mVerticalLineAlignment = alignment;
+}
+
 // public : Relayout.
 
 Controller::UpdateTextType Controller::Relayout( const Size& size )
@@ -2129,6 +2200,13 @@ Controller::UpdateTextType Controller::Relayout( const Size& size )
   {
     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", mImpl->mModel->mVisualModel->mControlSize.width, mImpl->mModel->mVisualModel->mControlSize.height );
 
+    if( ( 0 == mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd ) &&
+        ( 0 == mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters ) &&
+        ( ( mImpl->mModel->mVisualModel->mControlSize.width < Math::MACHINE_EPSILON_1000 ) || ( mImpl->mModel->mVisualModel->mControlSize.height < Math::MACHINE_EPSILON_1000 ) ) )
+    {
+      mImpl->mTextUpdateInfo.mNumberOfCharactersToAdd = mImpl->mModel->mLogicalModel->mText.Count();
+    }
+
     // Layout operations that need to be done if the size changes.
     mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
                                                              LAYOUT                    |
@@ -2320,6 +2398,7 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
   {
     int keyCode = keyEvent.keyCode;
     const std::string& keyString = keyEvent.keyPressed;
+    const std::string keyName = keyEvent.keyPressedName;
 
     const bool isNullKey = ( 0 == keyCode ) && ( keyString.empty() );
 
@@ -2365,14 +2444,47 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
       mImpl->mEventData->mCheckScrollAmount = true;
       Event event( Event::CURSOR_KEY_EVENT );
       event.p1.mInt = keyCode;
+      event.p2.mBool = keyEvent.IsShiftModifier();
       mImpl->mEventData->mEventQueue.push_back( event );
 
       // Will request for relayout.
       relayoutNeeded = true;
     }
-    else if( Dali::DALI_KEY_BACKSPACE == keyCode )
+    else if ( Dali::DevelKey::DALI_KEY_CONTROL_LEFT == keyCode || Dali::DevelKey::DALI_KEY_CONTROL_RIGHT == keyCode )
     {
-      textChanged = BackspaceKeyEvent();
+      // Left or Right Control key event is received before Ctrl-C/V/X key event is received
+      // If not handle it here, any selected text will be deleted
+
+      // Do nothing
+      return false;
+    }
+    else if ( keyEvent.IsCtrlModifier() )
+    {
+      bool consumed = false;
+      if (keyName == KEY_C_NAME)
+      {
+        // Ctrl-C to copy the selected text
+        TextPopupButtonTouched( Toolkit::TextSelectionPopup::COPY );
+        consumed = true;
+      }
+      else if (keyName == KEY_V_NAME)
+      {
+        // Ctrl-V to paste the copied text
+        TextPopupButtonTouched( Toolkit::TextSelectionPopup::PASTE );
+        consumed = true;
+      }
+      else if (keyName == KEY_X_NAME)
+      {
+        // Ctrl-X to cut the selected text
+        TextPopupButtonTouched( Toolkit::TextSelectionPopup::CUT );
+        consumed = true;
+      }
+      return consumed;
+    }
+    else if( ( Dali::DALI_KEY_BACKSPACE == keyCode ) ||
+             ( Dali::DevelKey::DALI_KEY_DELETE == keyCode ) )
+    {
+      textChanged = DeleteEvent( keyCode );
 
       // Will request for relayout.
       relayoutNeeded = true;
@@ -2686,7 +2798,17 @@ ImfManager::ImfCallbackData Controller::OnImfEvent( ImfManager& imfManager, cons
 
   if( retrieveText )
   {
-    mImpl->GetText( numberOfWhiteSpaces, text );
+    if( !mImpl->IsShowingPlaceholderText() )
+    {
+      // Retrieves the normal text string.
+      mImpl->GetText( numberOfWhiteSpaces, text );
+    }
+    else
+    {
+      // When the current text is Placeholder Text, the surrounding text should be empty string.
+      // It means DALi should send empty string ("") to IME.
+      text = "";
+    }
   }
 
   ImfManager::ImfCallbackData callbackData( ( retrieveText || retrieveCursor ), cursorPosition, text, false );
@@ -3319,10 +3441,11 @@ bool Controller::DoRelayout( const Size& size,
     const Vector<CharacterIndex>& glyphsToCharactersMap = mImpl->mModel->mVisualModel->mGlyphsToCharacters;
     const Vector<Length>& charactersPerGlyph = mImpl->mModel->mVisualModel->mCharactersPerGlyph;
     const Character* const textBuffer = mImpl->mModel->mLogicalModel->mText.Begin();
-    float outlineWidth = mImpl->mModel->GetOutlineWidth();
+    const float outlineWidth = static_cast<float>( mImpl->mModel->GetOutlineWidth() );
 
     // Set the layout parameters.
-    Layout::Parameters layoutParameters( size,
+    const Vector2 sizeOffset = Vector2(outlineWidth * 2.0f, outlineWidth * 2.0f); // The outline should be fit into the bounding box
+    Layout::Parameters layoutParameters( size - sizeOffset,
                                          textBuffer,
                                          lineBreakInfo.Begin(),
                                          wordBreakInfo.Begin(),
@@ -3389,7 +3512,7 @@ bool Controller::DoRelayout( const Size& size,
 
       if( NO_OPERATION != ( UPDATE_DIRECTION & operations ) )
       {
-        mImpl->mAutoScrollDirectionRTL = false;
+        mImpl->mIsTextDirectionRTL = false;
       }
 
       // Reorder the lines
@@ -3427,7 +3550,7 @@ bool Controller::DoRelayout( const Size& size,
             const LineRun* const firstline = mImpl->mModel->mVisualModel->mLines.Begin();
             if ( firstline )
             {
-              mImpl->mAutoScrollDirectionRTL = firstline->direction;
+              mImpl->mIsTextDirectionRTL = firstline->direction;
             }
           }
         }
@@ -3460,7 +3583,7 @@ bool Controller::DoRelayout( const Size& size,
 #if defined(DEBUG_ENABLED)
   std::string currentText;
   GetText( currentText );
-  DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::DoRelayout [%p] mImpl->mAutoScrollDirectionRTL[%s] [%s]\n", this, (mImpl->mAutoScrollDirectionRTL)?"true":"false",  currentText.c_str() );
+  DALI_LOG_INFO( gLogFilter, Debug::Concise, "Controller::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", this, (mImpl->mIsTextDirectionRTL)?"true":"false",  currentText.c_str() );
 #endif
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::DoRelayout, view updated %s\n", ( viewUpdated ? "true" : "false" ) );
   return viewUpdated;
@@ -3540,6 +3663,10 @@ void Controller::ProcessModifyEvents()
   {
     // When the text is being modified, delay cursor blinking
     mImpl->mEventData->mDecorator->DelayCursorBlink();
+
+    // Update selection position after modifying the text
+    mImpl->mEventData->mLeftSelectionPosition = mImpl->mEventData->mPrimaryCursorPosition;
+    mImpl->mEventData->mRightSelectionPosition = mImpl->mEventData->mPrimaryCursorPosition;
   }
 
   // Discard temporary text
@@ -3551,6 +3678,9 @@ void Controller::TextReplacedEvent()
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
+  // The text direction needs to be updated.
+  mImpl->mUpdateTextDirection = true;
+
   // Apply modifications to the model
   mImpl->mOperationsPending = ALL_OPERATIONS;
 }
@@ -3569,6 +3699,9 @@ void Controller::TextInsertedEvent()
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
+  // The text direction needs to be updated.
+  mImpl->mUpdateTextDirection = true;
+
   // Apply modifications to the model; TODO - Optimize this
   mImpl->mOperationsPending = ALL_OPERATIONS;
 }
@@ -3587,6 +3720,9 @@ void Controller::TextDeletedEvent()
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
+  // The text direction needs to be updated.
+  mImpl->mUpdateTextDirection = true;
+
   // Apply modifications to the model; TODO - Optimize this
   mImpl->mOperationsPending = ALL_OPERATIONS;
 }
@@ -3617,9 +3753,9 @@ void Controller::SelectEvent( float x, float y, bool selectAll )
   }
 }
 
-bool Controller::BackspaceKeyEvent()
+bool Controller::DeleteEvent( int keyCode )
 {
-  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p DALI_KEY_BACKSPACE\n", this );
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Controller::KeyEvent %p KeyCode : %d \n", this, keyCode );
 
   bool removed = false;
 
@@ -3635,13 +3771,20 @@ bool Controller::BackspaceKeyEvent()
   {
     removed = RemoveSelectedText();
   }
-  else if( mImpl->mEventData->mPrimaryCursorPosition > 0 )
+  else if( ( mImpl->mEventData->mPrimaryCursorPosition > 0 ) && ( keyCode == Dali::DALI_KEY_BACKSPACE) )
   {
     // Remove the character before the current cursor position
     removed = RemoveText( -1,
                           1,
                           UPDATE_INPUT_STYLE );
   }
+  else if( keyCode == Dali::DevelKey::DALI_KEY_DELETE )
+  {
+    // Remove the character after the current cursor position
+    removed = RemoveText( 0,
+                          1,
+                          UPDATE_INPUT_STYLE );
+  }
 
   if( removed )
   {
@@ -3681,6 +3824,9 @@ void Controller::ResetText()
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
+  // The text direction needs to be updated.
+  mImpl->mUpdateTextDirection = true;
+
   // Apply modifications to the model
   mImpl->mOperationsPending = ALL_OPERATIONS;
 }
@@ -3747,6 +3893,9 @@ void Controller::ShowPlaceholderText()
     // The natural size needs to be re-calculated.
     mImpl->mRecalculateNaturalSize = true;
 
+    // The text direction needs to be updated.
+    mImpl->mUpdateTextDirection = true;
+
     // Apply modifications to the model
     mImpl->mOperationsPending = ALL_OPERATIONS;
 
@@ -3774,6 +3923,7 @@ void Controller::ClearFontData()
   mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
                                                            VALIDATE_FONTS            |
                                                            SHAPE_TEXT                |
+                                                           BIDI_INFO                 |
                                                            GET_GLYPH_METRICS         |
                                                            LAYOUT                    |
                                                            UPDATE_LAYOUT_SIZE        |