Fix for Text::Controller.
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller.cpp
index 714a154..c16cd0b 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.
@@ -1313,6 +1313,63 @@ const std::string& Controller::GetInputOutlineProperties() const
   return GetDefaultOutlineProperties();
 }
 
+void Controller::SetInputModePassword( bool passwordInput )
+{
+  if( NULL != mImpl->mEventData )
+  {
+    mImpl->mEventData->mPasswordInput = passwordInput;
+  }
+}
+
+bool Controller::IsInputModePassword()
+{
+  if( NULL != mImpl->mEventData )
+  {
+    return mImpl->mEventData->mPasswordInput;
+  }
+  return false;
+}
+
+void Controller::SetNoTextDoubleTapAction( NoTextTap::Action action )
+{
+  if( NULL != mImpl->mEventData )
+  {
+    mImpl->mEventData->mDoubleTapAction = action;
+  }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextDoubleTapAction() const
+{
+  NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+  if( NULL != mImpl->mEventData )
+  {
+    action = mImpl->mEventData->mDoubleTapAction;
+  }
+
+  return action;
+}
+
+void Controller::SetNoTextLongPressAction( NoTextTap::Action action )
+{
+  if( NULL != mImpl->mEventData )
+  {
+    mImpl->mEventData->mLongPressAction = action;
+  }
+}
+
+Controller::NoTextTap::Action Controller::GetNoTextLongPressAction() const
+{
+  NoTextTap::Action action = NoTextTap::NO_ACTION;
+
+  if( NULL != mImpl->mEventData )
+  {
+    action = mImpl->mEventData->mLongPressAction;
+  }
+
+  return action;
+}
+
 // public : Queries & retrieves.
 
 Layout::Engine& Controller::GetLayoutEngine()
@@ -1344,16 +1401,17 @@ Vector3 Controller::GetNaturalSize()
                                                                            BIDI_INFO         |
                                                                            SHAPE_TEXT        |
                                                                            GET_GLYPH_METRICS );
+
+    // 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 );
 
     // Layout the text for the new width.
     mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | LAYOUT );
 
-    // Set the update info to relayout the whole text.
-    mImpl->mTextUpdateInfo.mParagraphCharacterIndex = 0u;
-    mImpl->mTextUpdateInfo.mRequestedNumberOfCharacters = mImpl->mModel->mLogicalModel->mText.Count();
-
     // Store the actual control's size to restore later.
     const Size actualControlSize = mImpl->mModel->mVisualModel->mControlSize;
 
@@ -1416,6 +1474,11 @@ float Controller::GetHeightForWidth( float width )
                                                                            BIDI_INFO         |
                                                                            SHAPE_TEXT        |
                                                                            GET_GLYPH_METRICS );
+
+    // 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 );
 
@@ -1423,10 +1486,6 @@ float Controller::GetHeightForWidth( float width )
     // Layout the text for the new width.
     mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | LAYOUT );
 
-    // Set the update info to relayout the whole text.
-    mImpl->mTextUpdateInfo.mParagraphCharacterIndex = 0u;
-    mImpl->mTextUpdateInfo.mRequestedNumberOfCharacters = mImpl->mModel->mLogicalModel->mText.Count();
-
     // Store the actual control's width.
     const float actualControlWidth = mImpl->mModel->mVisualModel->mControlSize.width;
 
@@ -1467,6 +1526,30 @@ const ModelInterface* const Controller::GetTextModel() const
   return mImpl->mModel.Get();
 }
 
+float Controller::GetScrollAmountByUserInput()
+{
+  float scrollAmount = 0.0f;
+
+  if (NULL != mImpl->mEventData && mImpl->mEventData->mCheckScrollAmount)
+  {
+    scrollAmount = mImpl->mModel->mScrollPosition.y -  mImpl->mModel->mScrollPositionLast.y;
+    mImpl->mEventData->mCheckScrollAmount = false;
+  }
+  return scrollAmount;
+}
+
+bool Controller::GetTextScrollInfo( float& scrollPosition, float& controlHeight, float& layoutHeight )
+{
+  const Vector2& layout = mImpl->mModel->mVisualModel->GetLayoutSize();
+  bool isScrolled;
+
+  controlHeight = mImpl->mModel->mVisualModel->mControlSize.height;
+  layoutHeight = layout.height;
+  scrollPosition = mImpl->mModel->mScrollPosition.y;
+  isScrolled = !Equals( mImpl->mModel->mScrollPosition.y, mImpl->mModel->mScrollPositionLast.y, Math::MACHINE_EPSILON_1 );
+  return isScrolled;
+}
+
 // public : Relayout.
 
 Controller::UpdateTextType Controller::Relayout( const Size& size )
@@ -1538,6 +1621,7 @@ Controller::UpdateTextType Controller::Relayout( const Size& size )
 
   // Do not re-do any operation until something changes.
   mImpl->mOperationsPending = NO_OPERATION;
+  mImpl->mModel->mScrollPositionLast = mImpl->mModel->mScrollPosition;
 
   // Whether the text control is editable
   const bool isEditable = NULL != mImpl->mEventData;
@@ -1675,8 +1759,15 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
     int keyCode = keyEvent.keyCode;
     const std::string& keyString = keyEvent.keyPressed;
 
+    const bool isNullKey = ( 0 == keyCode ) && ( keyString.empty() );
+
     // Pre-process to separate modifying events from non-modifying input events.
-    if( Dali::DALI_KEY_ESCAPE == keyCode )
+    if( isNullKey )
+    {
+      // In some platforms arrive key events with no key code.
+      // Do nothing.
+    }
+    else if( Dali::DALI_KEY_ESCAPE == keyCode )
     {
       // Escape key is a special case which causes focus loss
       KeyboardFocusLostEvent();
@@ -1686,6 +1777,29 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
              ( Dali::DALI_KEY_CURSOR_UP    == keyCode ) ||
              ( Dali::DALI_KEY_CURSOR_DOWN  == keyCode ) )
     {
+      // If don't have any text, do nothing.
+      if( !mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters )
+      {
+        return false;
+      }
+
+      uint32_t cursorPosition = mImpl->mEventData->mPrimaryCursorPosition;
+      uint32_t numberOfCharacters = mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
+      uint32_t cursorLine = mImpl->mModel->mVisualModel->GetLineOfCharacter( cursorPosition );
+      uint32_t numberOfLines = mImpl->mModel->GetNumberOfLines();
+
+      // Logic to determine whether this text control will lose focus or not.
+      if( ( Dali::DALI_KEY_CURSOR_LEFT == keyCode && 0 == cursorPosition ) ||
+          ( Dali::DALI_KEY_CURSOR_RIGHT == keyCode && numberOfCharacters == cursorPosition) ||
+          ( Dali::DALI_KEY_CURSOR_DOWN == keyCode && cursorLine == numberOfLines -1 ) ||
+          ( Dali::DALI_KEY_CURSOR_DOWN == keyCode && numberOfCharacters == cursorPosition && cursorLine -1 == numberOfLines -1 ) ||
+          ( Dali::DALI_KEY_CURSOR_UP == keyCode && cursorLine == 0 ) ||
+          ( Dali::DALI_KEY_CURSOR_UP == keyCode && numberOfCharacters == cursorPosition && cursorLine == 1 ) )
+      {
+        return false;
+      }
+
+      mImpl->mEventData->mCheckScrollAmount = true;
       Event event( Event::CURSOR_KEY_EVENT );
       event.p1.mInt = keyCode;
       mImpl->mEventData->mEventQueue.push_back( event );
@@ -1694,17 +1808,14 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
     {
       textChanged = BackspaceKeyEvent();
     }
-    else if( IsKey( keyEvent,  Dali::DALI_KEY_POWER ) )
-    {
-      mImpl->ChangeState( EventData::INTERRUPTED ); // State is not INACTIVE as expect to return to edit mode.
-      // Avoids calling the InsertText() method which can delete selected text
-    }
-    else if( IsKey( keyEvent, Dali::DALI_KEY_MENU ) ||
+    else if( IsKey( keyEvent, Dali::DALI_KEY_POWER ) ||
+             IsKey( keyEvent, Dali::DALI_KEY_MENU ) ||
              IsKey( keyEvent, Dali::DALI_KEY_HOME ) )
     {
+      // Power key/Menu/Home key behaviour does not allow edit mode to resume.
       mImpl->ChangeState( EventData::INACTIVE );
-      // Menu/Home key behaviour does not allow edit mode to resume like Power key
-      // Avoids calling the InsertText() method which can delete selected text
+
+      // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
     }
     else if( Dali::DALI_KEY_SHIFT_LEFT == keyCode )
     {
@@ -1726,6 +1837,7 @@ bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
 
     if ( ( mImpl->mEventData->mState != EventData::INTERRUPTED ) &&
          ( mImpl->mEventData->mState != EventData::INACTIVE ) &&
+         ( !isNullKey ) &&
          ( Dali::DALI_KEY_SHIFT_LEFT != keyCode ) )
     {
       // Should not change the state if the key is the shift send by the imf manager.
@@ -1801,9 +1913,12 @@ void Controller::TapEvent( unsigned int tapCount, float x, float y )
       if( mImpl->mEventData->mSelectionEnabled &&
           mImpl->IsShowingRealText() )
       {
-        SelectEvent( x, y, false );
+        relayoutNeeded = true;
+        mImpl->mEventData->mIsLeftHandleSelected = true;
+        mImpl->mEventData->mIsRightHandleSelected = true;
       }
     }
+
     // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
     if( relayoutNeeded )
     {
@@ -1844,35 +1959,42 @@ void Controller::LongPressEvent( Gesture::State state, float x, float y  )
   if( ( state == Gesture::Started ) &&
       ( NULL != mImpl->mEventData ) )
   {
-    if( !mImpl->IsShowingRealText() )
+    // The 1st long-press on inactive text-field is treated as tap
+    if( EventData::INACTIVE == mImpl->mEventData->mState )
+    {
+      mImpl->ChangeState( EventData::EDITING );
+
+      Event event( Event::TAP_EVENT );
+      event.p1.mUint = 1;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
+      mImpl->mEventData->mEventQueue.push_back( event );
+
+      mImpl->RequestRelayout();
+    }
+    else if( !mImpl->IsShowingRealText() )
     {
       Event event( Event::LONG_PRESS_EVENT );
       event.p1.mInt = state;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
       mImpl->mEventData->mEventQueue.push_back( event );
       mImpl->RequestRelayout();
     }
-    else
+    else if( !mImpl->IsClipboardVisible() )
     {
-      // The 1st long-press on inactive text-field is treated as tap
-      if( EventData::INACTIVE == mImpl->mEventData->mState )
-      {
-        mImpl->ChangeState( EventData::EDITING );
-
-        Event event( Event::TAP_EVENT );
-        event.p1.mUint = 1;
-        event.p2.mFloat = x;
-        event.p3.mFloat = y;
-        mImpl->mEventData->mEventQueue.push_back( event );
+      // Reset the imf manager to commit the pre-edit before selecting the text.
+      mImpl->ResetImfManager();
 
-        mImpl->RequestRelayout();
-      }
-      else if( !mImpl->IsClipboardVisible() )
-      {
-        // Reset the imf manger to commit the pre-edit before selecting the text.
-        mImpl->ResetImfManager();
+      Event event( Event::LONG_PRESS_EVENT );
+      event.p1.mInt = state;
+      event.p2.mFloat = x;
+      event.p3.mFloat = y;
+      mImpl->mEventData->mEventQueue.push_back( event );
+      mImpl->RequestRelayout();
 
-        SelectEvent( x, y, false );
-      }
+      mImpl->mEventData->mIsLeftHandleSelected = true;
+      mImpl->mEventData->mIsRightHandleSelected = true;
     }
   }
 }
@@ -2154,8 +2276,9 @@ void Controller::TextPopupButtonTouched( Dali::Toolkit::TextSelectionPopup::Butt
 
 void Controller::InsertText( const std::string& text, Controller::InsertType type )
 {
-  bool removedPrevious( false );
-  bool maxLengthReached( false );
+  bool removedPrevious = false;
+  bool removedSelected = false;
+  bool maxLengthReached = false;
 
   DALI_ASSERT_DEBUG( NULL != mImpl->mEventData && "Unexpected InsertText" )
 
@@ -2171,9 +2294,6 @@ void Controller::InsertText( const std::string& text, Controller::InsertType typ
   // TODO: At the moment the underline runs are only for pre-edit.
   mImpl->mModel->mVisualModel->mUnderlineRuns.Clear();
 
-  // Keep the current number of characters.
-  const Length currentNumberOfCharacters = mImpl->IsShowingRealText() ? mImpl->mModel->mLogicalModel->mText.Count() : 0u;
-
   // Remove the previous IMF pre-edit.
   if( mImpl->mEventData->mPreEditFlag && ( 0u != mImpl->mEventData->mPreEditLength ) )
   {
@@ -2187,7 +2307,8 @@ void Controller::InsertText( const std::string& text, Controller::InsertType typ
   else
   {
     // Remove the previous Selection.
-    removedPrevious = RemoveSelectedText();
+    removedSelected = RemoveSelectedText();
+
   }
 
   Vector<Character> utf32Characters;
@@ -2357,8 +2478,6 @@ void Controller::InsertText( const std::string& text, Controller::InsertType typ
     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Inserted %d characters, new size %d new cursor %d\n", maxSizeOfNewText, mImpl->mModel->mLogicalModel->mText.Count(), mImpl->mEventData->mPrimaryCursorPosition );
   }
 
-  const Length numberOfCharacters = mImpl->IsShowingRealText() ? mImpl->mModel->mLogicalModel->mText.Count() : 0u;
-
   if( ( 0u == mImpl->mModel->mLogicalModel->mText.Count() ) &&
       mImpl->IsPlaceholderAvailable() )
   {
@@ -2368,13 +2487,14 @@ void Controller::InsertText( const std::string& text, Controller::InsertType typ
     mImpl->ClearPreEditFlag();
   }
   else if( removedPrevious ||
+           removedSelected ||
            ( 0 != utf32Characters.Count() ) )
   {
     // Queue an inserted event
     mImpl->QueueModifyEvent( ModifyEvent::TEXT_INSERTED );
 
     mImpl->mEventData->mUpdateCursorPosition = true;
-    if( numberOfCharacters < currentNumberOfCharacters )
+    if( removedSelected )
     {
       mImpl->mEventData->mScrollAfterDelete = true;
     }
@@ -2444,7 +2564,8 @@ bool Controller::RemoveText( int cursorOffset,
       numberOfCharacters = currentText.Count() - cursorIndex;
     }
 
-    if( ( cursorIndex + numberOfCharacters ) <= mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters )
+    if( mImpl->mEventData->mPreEditFlag || // If the preedit flag is enabled, it means two (or more) of them came together i.e. when two keys have been pressed at the same time.
+        ( ( cursorIndex + numberOfCharacters ) <= mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters ) )
     {
       // Mark the paragraphs to be updated.
       mImpl->mTextUpdateInfo.mCharacterIndex = std::min( cursorIndex, mImpl->mTextUpdateInfo.mCharacterIndex );
@@ -2674,11 +2795,14 @@ bool Controller::DoRelayout( const Size& size,
     // The laid-out lines.
     Vector<LineRun>& lines = mImpl->mModel->mVisualModel->mLines;
 
+    // Need to align with the control's size as the text may contain lines
+    // starting either with left to right text or right to left.
     mImpl->mLayoutEngine.Align( size,
                                 startIndex,
                                 requestedNumberOfCharacters,
                                 mImpl->mModel->mHorizontalAlignment,
-                                lines );
+                                lines,
+                                mImpl->mModel->mAlignmentOffset );
 
     viewUpdated = true;
   }
@@ -2789,6 +2913,8 @@ void Controller::TextInsertedEvent()
     return;
   }
 
+  mImpl->mEventData->mCheckScrollAmount = true;
+
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
@@ -2805,6 +2931,8 @@ void Controller::TextDeletedEvent()
     return;
   }
 
+  mImpl->mEventData->mCheckScrollAmount = true;
+
   // The natural size needs to be re-calculated.
   mImpl->mRecalculateNaturalSize = true;
 
@@ -2831,6 +2959,9 @@ void Controller::SelectEvent( float x, float y, bool selectAll )
       mImpl->mEventData->mEventQueue.push_back( event );
     }
 
+    mImpl->mEventData->mCheckScrollAmount = true;
+    mImpl->mEventData->mIsLeftHandleSelected = true;
+    mImpl->mEventData->mIsRightHandleSelected = true;
     mImpl->RequestRelayout();
   }
 }