[Tizen](ATSPI) squashed implementation
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-controls / text-editor-impl.cpp
old mode 100644 (file)
new mode 100755 (executable)
index 77974d5..436c9ff
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2018 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.
@@ -44,6 +44,7 @@
 #include <dali-toolkit/internal/text/text-font-style.h>
 #include <dali-toolkit/internal/text/text-view.h>
 #include <dali-toolkit/internal/styling/style-manager-impl.h>
+#include <dali-toolkit/devel-api/controls/control-devel.h>
 
 using namespace Dali::Toolkit::Text;
 
@@ -136,6 +137,8 @@ DALI_PROPERTY_REGISTRATION( Toolkit, TextEditor, "lineWrapMode",
 DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextEditor, "placeholderText",                STRING,    PLACEHOLDER_TEXT                     )
 DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextEditor, "placeholderTextColor",           VECTOR4,   PLACEHOLDER_TEXT_COLOR               )
 DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextEditor, "enableShiftSelection",           BOOLEAN,   ENABLE_SHIFT_SELECTION               )
+DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextEditor, "enableGrabHandle",               BOOLEAN,   ENABLE_GRAB_HANDLE                   )
+DALI_DEVEL_PROPERTY_REGISTRATION( Toolkit, TextEditor, "matchSystemLanguageDirection",   BOOLEAN,   MATCH_SYSTEM_LANGUAGE_DIRECTION      )
 
 DALI_SIGNAL_REGISTRATION( Toolkit, TextEditor, "textChanged",        SIGNAL_TEXT_CHANGED )
 DALI_SIGNAL_REGISTRATION( Toolkit, TextEditor, "inputStyleChanged",  SIGNAL_INPUT_STYLE_CHANGED )
@@ -512,9 +515,9 @@ void TextEditor::SetProperty( BaseObject* object, Property::Index index, const P
         if( impl.mController )
         {
 
-          // The line spacing isn't supported by the TextEditor. Since it's supported
-          // by the TextLabel for now it must be ignored. The property is being shadowed
-          // locally so its value isn't affected.
+          // The line spacing isn't supported by the TextEditor. Since it's
+          // supported by the TextEditor for now it must be ignored. The
+          // property is being shadowed locally so its value isn't affected.
           const float lineSpacing = value.Get<float>();
           impl.mLineSpacing = lineSpacing;
           // set it to 0.0 due to missing implementation
@@ -732,6 +735,25 @@ void TextEditor::SetProperty( BaseObject* object, Property::Index index, const P
         }
         break;
       }
+      case Toolkit::DevelTextEditor::Property::ENABLE_GRAB_HANDLE:
+      {
+        if( impl.mController )
+        {
+          const bool grabHandleEnabled = value.Get<bool>();
+          DALI_LOG_INFO( gLogFilter, Debug::General, "TextEditor %p ENABLE_GRAB_HANDLE %d\n", impl.mController.Get(), grabHandleEnabled );
+
+          impl.mController->SetGrabHandleEnabled( grabHandleEnabled );
+        }
+        break;
+      }
+      case Toolkit::DevelTextEditor::Property::MATCH_SYSTEM_LANGUAGE_DIRECTION:
+      {
+        if( impl.mController )
+        {
+          impl.mController->SetMatchSystemLanguageDirection(value.Get< bool >());
+        }
+        break;
+      }
     } // switch
   } // texteditor
 }
@@ -1124,12 +1146,38 @@ Property::Value TextEditor::GetProperty( BaseObject* object, Property::Index ind
         }
         break;
       }
+      case Toolkit::DevelTextEditor::Property::ENABLE_GRAB_HANDLE:
+      {
+        if( impl.mController )
+        {
+          value = impl.mController->IsGrabHandleEnabled();
+        }
+        break;
+      }
+      case Toolkit::DevelTextEditor::Property::MATCH_SYSTEM_LANGUAGE_DIRECTION:
+      {
+        if( impl.mController )
+        {
+          value = impl.mController->IsMatchSystemLanguageDirection();
+        }
+        break;
+      }
     } //switch
   }
 
   return value;
 }
 
+InputMethodContext TextEditor::GetInputMethodContext()
+{
+  return mInputMethodContext;
+}
+
+Text::ControllerPtr TextEditor::getController()
+{
+  return mController;
+}
+
 bool TextEditor::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
 {
   Dali::BaseHandle handle( object );
@@ -1178,10 +1226,12 @@ void TextEditor::OnInitialize()
   mDecorator = Text::Decorator::New( *mController,
                                      *mController );
 
+  mInputMethodContext = InputMethodContext::New( self );
+
   mController->GetLayoutEngine().SetLayout( Layout::Engine::MULTI_LINE_BOX );
 
   // Enables the text input.
-  mController->EnableTextInput( mDecorator );
+  mController->EnableTextInput( mDecorator, mInputMethodContext );
 
   // Enables the vertical scrolling after the text input has been enabled.
   mController->SetVerticalScrollEnabled( true );
@@ -1198,12 +1248,15 @@ void TextEditor::OnInitialize()
   mController->SetNoTextDoubleTapAction( Controller::NoTextTap::HIGHLIGHT );
   mController->SetNoTextLongPressAction( Controller::NoTextTap::HIGHLIGHT );
 
+  // Sets layoutDirection value
+  Dali::Stage stage = Dali::Stage::GetCurrent();
+  Dali::LayoutDirection::Type layoutDirection = static_cast<Dali::LayoutDirection::Type>( stage.GetRootLayer().GetProperty( Dali::Actor::Property::LAYOUT_DIRECTION ).Get<int>() );
+  mController->SetLayoutDirection( layoutDirection );
+
   // Forward input events to controller
   EnableGestureDetection( static_cast<Gesture::Type>( Gesture::Tap | Gesture::Pan | Gesture::LongPress ) );
   GetTapGestureDetector().SetMaximumTapsRequired( 2 );
 
-  mImfManager = ImfManager::Get();
-
   self.TouchSignal().Connect( this, &TextEditor::OnTouched );
 
   // Set BoundingBox to stage size if not already set.
@@ -1227,6 +1280,8 @@ void TextEditor::OnInitialize()
   self.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::HEIGHT );
   self.OnStageSignal().Connect( this, &TextEditor::OnStageConnect );
 
+  DevelControl::SetInputMethodContext( *this, mInputMethodContext );
+
   // Creates an extra control to be used as stencil buffer.
   mStencil = Control::New();
   mStencil.SetAnchorPoint( AnchorPoint::TOP_LEFT );
@@ -1238,7 +1293,7 @@ void TextEditor::OnInitialize()
                         Add( ColorVisual::Property::MIX_COLOR, Color::TRANSPARENT ) );
 
   // Enable the clipping property.
-  mStencil.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_CHILDREN );
+  mStencil.SetProperty( Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX );
   mStencil.SetResizePolicy( ResizePolicy::FILL_TO_PARENT, Dimension::ALL_DIMENSIONS );
 
   self.Add( mStencil );
@@ -1323,7 +1378,7 @@ void TextEditor::OnRelayout( const Vector2& size, RelayoutContainer& container )
     mActiveLayer.SetPosition( padding.start, padding.top );
   }
 
-  const Text::Controller::UpdateTextType updateTextType = mController->Relayout( contentSize );
+  const Text::Controller::UpdateTextType updateTextType = mController->Relayout( contentSize, layoutDirection );
 
   if( ( Text::Controller::NONE_UPDATED != updateTextType ) ||
       !mRenderer )
@@ -1359,7 +1414,7 @@ void TextEditor::OnRelayout( const Vector2& size, RelayoutContainer& container )
       {
         // @note: The callback manager takes the ownership of the callback object.
         mIdleCallback = MakeCallback( this, &TextEditor::OnIdleSignal );
-        adaptor.AddIdle( mIdleCallback );
+        adaptor.AddIdle( mIdleCallback, false );
       }
     }
   }
@@ -1414,23 +1469,24 @@ void TextEditor::RenderText( Text::Controller::UpdateTextType updateTextType )
 void TextEditor::OnKeyInputFocusGained()
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextEditor::OnKeyInputFocusGained %p\n", mController.Get() );
+  if ( mInputMethodContext )
+  {
+    mInputMethodContext.StatusChangedSignal().Connect( this, &TextEditor::KeyboardStatusChanged );
 
-  mImfManager.StatusChangedSignal().Connect( this, &TextEditor::KeyboardStatusChanged );
-
-  mImfManager.EventReceivedSignal().Connect( this, &TextEditor::OnImfEvent );
-
-  // Notify that the text editing start.
-  mImfManager.Activate();
+    mInputMethodContext.EventReceivedSignal().Connect( this, &TextEditor::OnInputMethodContextEvent );
 
-  // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
-  mImfManager.SetRestoreAfterFocusLost( true );
+    // Notify that the text editing start.
+    mInputMethodContext.Activate();
 
-   ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
+    // When window gain lost focus, the InputMethodContext is deactivated. Thus when window gain focus again, the InputMethodContext must be activated.
+    mInputMethodContext.SetRestoreAfterFocusLost( true );
+  }
+  ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
 
-   if ( notifier )
-   {
-      notifier.ContentSelectedSignal().Connect( this, &TextEditor::OnClipboardTextSelected );
-   }
+  if ( notifier )
+  {
+    notifier.ContentSelectedSignal().Connect( this, &TextEditor::OnClipboardTextSelected );
+  }
 
   mController->KeyboardFocusGainEvent(); // Called in the case of no virtual keyboard to trigger this event
 
@@ -1440,17 +1496,18 @@ void TextEditor::OnKeyInputFocusGained()
 void TextEditor::OnKeyInputFocusLost()
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextEditor:OnKeyInputFocusLost %p\n", mController.Get() );
+  if ( mInputMethodContext )
+  {
+    mInputMethodContext.StatusChangedSignal().Disconnect( this, &TextEditor::KeyboardStatusChanged );
 
-  mImfManager.StatusChangedSignal().Disconnect( this, &TextEditor::KeyboardStatusChanged );
-
-  // The text editing is finished. Therefore the imf manager don't have restore activation.
-  mImfManager.SetRestoreAfterFocusLost( false );
-
-  // Notify that the text editing finish.
-  mImfManager.Deactivate();
+    // The text editing is finished. Therefore the InputMethodContext don't have restore activation.
+    mInputMethodContext.SetRestoreAfterFocusLost( false );
 
-  mImfManager.EventReceivedSignal().Disconnect( this, &TextEditor::OnImfEvent );
+    // Notify that the text editing finish.
+    mInputMethodContext.Deactivate();
 
+    mInputMethodContext.EventReceivedSignal().Disconnect( this, &TextEditor::OnInputMethodContextEvent );
+  }
   ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
 
   if ( notifier )
@@ -1466,9 +1523,10 @@ void TextEditor::OnKeyInputFocusLost()
 void TextEditor::OnTap( const TapGesture& gesture )
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextEditor::OnTap %p\n", mController.Get() );
-
-  mImfManager.Activate();
-
+  if ( mInputMethodContext )
+  {
+    mInputMethodContext.Activate();
+  }
   // Deliver the tap before the focus event to controller; this allows us to detect when focus is gained due to tap-gestures
   Extents padding;
   padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
@@ -1484,8 +1542,10 @@ void TextEditor::OnPan( const PanGesture& gesture )
 
 void TextEditor::OnLongPress( const LongPressGesture& gesture )
 {
-  mImfManager.Activate();
-
+  if ( mInputMethodContext )
+  {
+    mInputMethodContext.Activate();
+  }
   Extents padding;
   padding = Self().GetProperty<Extents>( Toolkit::Control::Property::PADDING );
   mController->LongPressEvent( gesture.state, gesture.localPoint.x - padding.start, gesture.localPoint.y - padding.top );
@@ -1516,6 +1576,30 @@ void TextEditor::RequestTextRelayout()
   RelayoutRequest();
 }
 
+void TextEditor::TextInserted( unsigned int position, unsigned int length, const std::string &content )
+{
+  if( Accessibility::IsUp() )
+  {
+    Control::Impl::GetAccessibilityObject( Self() )->EmitTextInserted( position, length, content );
+  }
+}
+
+void TextEditor::TextDeleted( unsigned int position, unsigned int length, const std::string &content )
+{
+  if( Accessibility::IsUp() )
+  {
+       Control::Impl::GetAccessibilityObject( Self() )->EmitTextDeleted( position, length, content );
+  }
+}
+
+void TextEditor::CaretMoved( unsigned int position )
+{
+  if( Accessibility::IsUp() )
+  {
+    Control::Impl::GetAccessibilityObject( Self() )->EmitTextCaretMoved( position );
+  }
+}
+
 void TextEditor::TextChanged()
 {
   Dali::Toolkit::TextEditor handle( GetOwner() );
@@ -1707,10 +1791,10 @@ void TextEditor::OnStageConnect( Dali::Actor actor )
   }
 }
 
-ImfManager::ImfCallbackData TextEditor::OnImfEvent( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
+InputMethodContext::CallbackData TextEditor::OnInputMethodContextEvent( Dali::InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent )
 {
-  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextEditor::OnImfEvent %p eventName %d\n", mController.Get(), imfEvent.eventName );
-  return mController->OnImfEvent( imfManager, imfEvent );
+  DALI_LOG_INFO( gLogFilter, Debug::Verbose, "TextEditor::OnInputMethodContextEvent %p eventName %d\n", mController.Get(), inputMethodContextEvent.eventName );
+  return mController->OnInputMethodContextEvent( inputMethodContext, inputMethodContextEvent );
 }
 
 void TextEditor::GetHandleImagePropertyValue(  Property::Value& value, Text::HandleType handleType, Text::HandleImageType handleImageType )
@@ -1814,6 +1898,10 @@ TextEditor::TextEditor()
   mScrollBarEnabled( false ),
   mScrollStarted( false )
 {
+  DevelControl::SetAccessibilityConstructor( Self(), []( Dali::Actor actor ) {
+    return std::unique_ptr< Dali::Accessibility::Accessible >(
+        new AccessibleImpl( actor, Dali::Accessibility::Role::ENTRY ) );
+  } );
 }
 
 TextEditor::~TextEditor()
@@ -1827,6 +1915,212 @@ TextEditor::~TextEditor()
   }
 }
 
+std::string TextEditor::AccessibleImpl::GetName()
+{
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  return slf.GetProperty( Toolkit::TextEditor::Property::TEXT )
+      .Get< std::string >();
+}
+
+std::string TextEditor::AccessibleImpl::GetText( size_t startOffset,
+                                                 size_t endOffset )
+{
+  if( endOffset <= startOffset )
+    return {};
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt =
+      slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get< std::string >();
+
+  if( startOffset > txt.size() || endOffset > txt.size() )
+    return {};
+
+  return txt.substr( startOffset, endOffset - startOffset );
+}
+
+size_t TextEditor::AccessibleImpl::GetCharacterCount()
+{
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt =
+      slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get< std::string >();
+
+  return txt.size();
+}
+
+size_t TextEditor::AccessibleImpl::GetCaretOffset()
+{
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  return Dali::Toolkit::GetImpl( slf ).getController()->GetCursorPosition();
+}
+
+bool TextEditor::AccessibleImpl::SetCaretOffset(size_t offset)
+{
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt = slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get< std::string >();
+  if (offset > txt.size())
+    return false;
+
+  auto& slfImpl = Dali::Toolkit::GetImpl( slf );
+  slfImpl.getController()->ResetCursorPosition( offset );
+  slfImpl.RequestTextRelayout();
+  return true;
+}
+
+Dali::Accessibility::Range TextEditor::AccessibleImpl::GetTextAtOffset(
+    size_t offset, Dali::Accessibility::TextBoundary boundary )
+{
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt = slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get< std::string >();
+  auto txt_size = txt.size();
+
+  auto range = Dali::Accessibility::Range{};
+
+  switch(boundary)
+  {
+    case Dali::Accessibility::TextBoundary::CHARACTER:
+      {
+        if (offset < txt_size)
+        {
+          range.content = txt[offset];
+          range.startOffset = offset;
+          range.endOffset = offset + 1;
+        }
+      }
+      break;
+    case Dali::Accessibility::TextBoundary::WORD:
+    case Dali::Accessibility::TextBoundary::LINE:
+      {
+        auto txt_c_string = txt.c_str();
+        auto breaks = std::vector< char >( txt_size, 0 );
+        if(boundary == Dali::Accessibility::TextBoundary::WORD)
+          Accessibility::Accessible::FindWordSeparationsUtf8((const utf8_t *) txt_c_string, txt_size, "", breaks.data());
+        else
+          Accessibility::Accessible::FindLineSeparationsUtf8((const utf8_t *) txt_c_string, txt_size, "", breaks.data());
+        auto index = 0u;
+        auto counter = 0u;
+        while( index < txt_size && counter <= offset )
+        {
+          auto start = index;
+          if(breaks[index])
+          {
+            while(breaks[index])
+              index++;
+            counter++;
+          }
+          else
+          {
+            if (boundary == Dali::Accessibility::TextBoundary::WORD)
+              index++;
+            if (boundary == Dali::Accessibility::TextBoundary::LINE)
+              counter++;
+          }
+          if ((counter - 1) == offset)
+          {
+            range.content = txt.substr(start, index - start + 1);
+            range.startOffset = start;
+            range.endOffset = index + 1;
+          }
+          if (boundary == Dali::Accessibility::TextBoundary::LINE)
+              index++;
+        }
+      }
+      break;
+    case Dali::Accessibility::TextBoundary::SENTENCE:
+      {
+        /* not supported by efl */
+      }
+      break;
+    case Dali::Accessibility::TextBoundary::PARAGRAPH:
+      {
+        /* Paragraph is not supported by libunibreak library */
+      }
+      break;
+    default:
+      break;
+  }
+
+  return range;
+}
+
+Dali::Accessibility::Range
+TextEditor::AccessibleImpl::GetSelection( size_t selectionNum )
+{
+  // Since DALi supports only one selection indexes higher than 0 are ignored
+  if( selectionNum > 0 )
+    return {};
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto ctrl = Dali::Toolkit::GetImpl( slf ).getController();
+  std::string ret;
+  ctrl->RetrieveSelection( ret );
+  auto r = ctrl->GetSelectionIndexes();
+
+  return { static_cast<size_t>(r.first), static_cast<size_t>(r.second), ret };
+}
+
+bool TextEditor::AccessibleImpl::RemoveSelection( size_t selectionNum )
+{
+  // Since DALi supports only one selection indexes higher than 0 are ignored
+  if( selectionNum > 0 )
+    return false;
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  Dali::Toolkit::GetImpl( slf ).getController()->SetSelection( 0, 0 );
+  return true;
+}
+
+bool TextEditor::AccessibleImpl::SetSelection( size_t selectionNum,
+                                               size_t startOffset,
+                                               size_t endOffset )
+{
+  // Since DALi supports only one selection indexes higher than 0 are ignored
+  if( selectionNum > 0 )
+    return false;
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  Dali::Toolkit::GetImpl( slf ).getController()->SetSelection( startOffset,
+                                                               endOffset );
+  return true;
+}
+
+bool TextEditor::AccessibleImpl::CopyText( size_t startPosition,
+                                           size_t endPosition )
+{
+  if( endPosition <= startPosition )
+    return false;
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt = slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get<std::string>();
+  Dali::Toolkit::GetImpl( slf ).getController()->CopyStringToClipboard( txt.substr(startPosition, endPosition - startPosition) );
+
+  return true;
+}
+
+bool TextEditor::AccessibleImpl::CutText( size_t startPosition,
+                                          size_t endPosition )
+{
+  if( endPosition <= startPosition )
+    return false;
+
+  auto slf = Toolkit::TextEditor::DownCast( self );
+  auto txt = slf.GetProperty( Toolkit::TextEditor::Property::TEXT ).Get<std::string>();
+  Dali::Toolkit::GetImpl( slf ).getController()->CopyStringToClipboard( txt.substr(startPosition, endPosition - startPosition) );
+
+  slf.SetProperty( Toolkit::TextEditor::Property::TEXT,
+                   txt.substr( 0, startPosition ) + txt.substr( endPosition - startPosition, txt.size()));
+
+  return true;
+}
+
+Dali::Accessibility::States TextEditor::AccessibleImpl::CalculateStates()
+{
+  auto states = Control::Impl::AccessibleImpl::CalculateStates();
+  using namespace Dali::Accessibility;
+  states[State::EDITABLE] = true;
+
+  return states;
+}
+
 } // namespace Internal
 
 } // namespace Toolkit