Implement Wayland IMFmanager 08/57408/2
authorsuhyung Eom <suhyung.eom@samsung.com>
Wed, 20 Jan 2016 01:39:53 +0000 (10:39 +0900)
committersuhyung Eom <suhyung.eom@samsung.com>
Tue, 26 Jan 2016 06:01:45 +0000 (15:01 +0900)
Signed-off-by: suhyung Eom <suhyung.eom@samsung.com>
Change-Id: I1e4752c351624b2a8dc5ac203371df9dc9c68919

adaptors/ecore/wayland/imf-manager-impl-ecore-wl.cpp
adaptors/ecore/wayland/imf-manager-impl.h
adaptors/ecore/wayland/virtual-keyboard-impl-ecore-wl.cpp
adaptors/ecore/wayland/window-impl-ecore-wl.cpp

index 8673c2c..833b125 100644 (file)
@@ -29,6 +29,7 @@
 #include <adaptor-impl.h>
 #include <singleton-service-impl.h>
 #include <virtual-keyboard-impl.h>
+#include "ecore-virtual-keyboard.h"
 
 namespace Dali
 {
@@ -39,47 +40,283 @@ namespace Internal
 namespace Adaptor
 {
 
+namespace
+{
+#if defined(DEBUG_ENABLED)
+Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMF_MANAGER");
+#endif
+
+// Currently this code is internal to dali/dali/internal/event/text/utf8.h but should be made Public and used from there instead.
+size_t Utf8SequenceLength(const unsigned char leadByte)
+{
+  size_t length = 0;
+
+  if ((leadByte & 0x80) == 0 )          //ASCII character (lead bit zero)
+  {
+    length = 1;
+  }
+  else if (( leadByte & 0xe0 ) == 0xc0 ) //110x xxxx
+  {
+    length = 2;
+  }
+  else if (( leadByte & 0xf0 ) == 0xe0 ) //1110 xxxx
+  {
+    length = 3;
+  }
+  else if (( leadByte & 0xf8 ) == 0xf0 ) //1111 0xxx
+  {
+    length = 4;
+  }
+
+  return length;
+}
+
+// Static function calls used by ecore 'c' style callback registration
+void Commit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
+{
+  if ( data )
+  {
+    ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
+    imfManager->CommitReceived( data, imfContext, event_info );
+  }
+}
+
+void PreEdit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
+{
+  if ( data )
+  {
+    ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
+    imfManager->PreEditChanged( data, imfContext, event_info );
+  }
+}
+
+Eina_Bool ImfRetrieveSurrounding(void *data, Ecore_IMF_Context *imfContext, char** text, int* cursorPosition )
+{
+  if ( data )
+  {
+    ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
+    return imfManager->RetrieveSurrounding( data, imfContext, text, cursorPosition );
+  }
+  else
+  {
+    return false;
+  }
+}
+
+/**
+ * Called when an IMF delete surrounding event is received.
+ * Here we tell the application that it should delete a certain range.
+ */
+void ImfDeleteSurrounding( void *data, Ecore_IMF_Context *imfContext, void *event_info )
+{
+  if ( data )
+  {
+    ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
+    imfManager->DeleteSurrounding( data, imfContext, event_info );
+  }
+}
+
+BaseHandle Create()
+{
+  return ImfManager::Get();
+}
+
+TypeRegistration IMF_MANAGER_TYPE( typeid(Dali::ImfManager), typeid(Dali::BaseHandle), Create );
+
+} // unnamed namespace
+
 bool ImfManager::IsAvailable()
 {
-  return false;
+  bool available( false );
+
+  Dali::SingletonService service( SingletonService::Get() );
+  if ( service )
+  {
+    available = service.GetSingleton( typeid( Dali::ImfManager ) );
+  }
+
+  return available;
 }
 
 Dali::ImfManager ImfManager::Get()
 {
-  // Return empty handle as not supported
-  return Dali::ImfManager();
+  Dali::ImfManager manager;
+
+  Dali::SingletonService service( SingletonService::Get() );
+  if ( service )
+  {
+    // Check whether the singleton is already created
+    Dali::BaseHandle handle = service.GetSingleton( typeid( Dali::ImfManager ) );
+    if( handle )
+    {
+      // If so, downcast the handle
+      manager = Dali::ImfManager( dynamic_cast< ImfManager* >( handle.GetObjectPtr() ) );
+    }
+    else if ( Adaptor::IsAvailable() )
+    {
+      // Create instance and register singleton only if the adaptor is available
+
+      Adaptor& adaptorImpl( Adaptor::GetImplementation( Adaptor::Get() ) );
+      Any nativeWindow = adaptorImpl.GetNativeWindowHandle();
+
+      // The Ecore_Wl_Window needs to use the ImfManager.
+      // Only when the render surface is window, we can get the Ecore_Wl_Window.
+      Ecore_Wl_Window *ecoreWwin( AnyCast< Ecore_Wl_Window* >( nativeWindow ) );
+      if (ecoreWwin)
+      {
+        // If we fail to get Ecore_Wl_Window, we can't use the ImfManager correctly.
+        // Thus you have to call "ecore_imf_context_client_window_set" somewhere.
+        // In EvasPlugIn, this function is called in EvasPlugin::ConnectEcoreEvent().
+
+        manager = Dali::ImfManager( new ImfManager( ecoreWwin ) );
+        service.Register( typeid( manager ), manager );
+      }
+      else
+      {
+        DALI_LOG_ERROR("Failed to get native window handle");
+      }
+    }
+  }
+
+  return manager;
+}
+
+ImfManager::ImfManager( Ecore_Wl_Window *ecoreWlwin )
+: mIMFContext(),
+  mIMFCursorPosition( 0 ),
+  mSurroundingText(),
+  mRestoreAfterFocusLost( false ),
+  mIdleCallbackConnected( false )
+{
+  ecore_imf_init();
+  CreateContext( ecoreWlwin );
+
+  ConnectCallbacks();
+  VirtualKeyboard::ConnectCallbacks( mIMFContext );
 }
 
 ImfManager::~ImfManager()
 {
+  VirtualKeyboard::DisconnectCallbacks( mIMFContext );
+  DisconnectCallbacks();
+
+  DeleteContext();
+  ecore_imf_shutdown();
+}
+
+
+void ImfManager::CreateContext( Ecore_Wl_Window *ecoreWlwin )
+{
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CreateContext\n" );
+
+  const char *contextId = ecore_imf_context_default_id_get();
+  if( contextId )
+  {
+    mIMFContext = ecore_imf_context_add( contextId );
+
+    if( mIMFContext )
+    {
+      if( ecoreWlwin )
+      {
+          ecore_imf_context_client_window_set( mIMFContext,
+            reinterpret_cast<void*>( ecore_wl_window_id_get(ecoreWlwin)) );
+      }
+    }
+    else
+    {
+      DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
+    }
+  }
+  else
+  {
+    DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
+  }
 }
 
 void ImfManager::DeleteContext()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteContext\n" );
+
+  if ( mIMFContext )
+  {
+    mIMFContext = NULL;
+  }
 }
 
+// Callbacks for predicitive text support.
 void ImfManager::ConnectCallbacks()
 {
+  if ( mIMFContext )
+  {
+    DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::ConnectCallbacks\n" );
+
+    ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit,    this );
+    ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit,     this );
+    ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding, this );
+
+    ecore_imf_context_retrieve_surrounding_callback_set( mIMFContext, ImfRetrieveSurrounding, this);
+  }
 }
 
 void ImfManager::DisconnectCallbacks()
 {
+  if ( mIMFContext )
+  {
+    DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DisconnectCallbacks\n" );
+
+    ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit );
+    ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit );
+    ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding );
+
+    // We do not need to unset the retrieve surrounding callback.
+  }
 }
 
 void ImfManager::Activate()
 {
+  // Reset mIdleCallbackConnected
+  mIdleCallbackConnected = false;
+
+  if ( mIMFContext )
+  {
+    DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Activate\n" );
+
+    ecore_imf_context_focus_in( mIMFContext );
+
+    // emit keyboard activated signal
+    Dali::ImfManager handle( this );
+    mActivatedSignal.Emit( handle );
+  }
 }
 
 void ImfManager::Deactivate()
 {
+  if( mIMFContext )
+  {
+    DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Deactivate\n" );
+
+    Reset();
+    ecore_imf_context_focus_out( mIMFContext );
+  }
+
+  // Reset mIdleCallbackConnected
+  mIdleCallbackConnected = false;
 }
 
 void ImfManager::Reset()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Reset\n" );
+
+  if ( mIMFContext )
+  {
+    ecore_imf_context_reset( mIMFContext );
+  }
 }
 
 Ecore_IMF_Context* ImfManager::GetContext()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetContext\n" );
+
   return mIMFContext;
 }
 
@@ -93,44 +330,189 @@ void ImfManager::SetRestoreAfterFocusLost( bool toggle )
   mRestoreAfterFocusLost = toggle;
 }
 
-void ImfManager::PreEditChanged( void *, Ecore_IMF_Context *imfContext, void *event_info )
+/**
+ * Called when an IMF Pre-Edit changed event is received.
+ * We are still predicting what the user is typing.  The latest string is what the IMF module thinks
+ * the user wants to type.
+ */
+void ImfManager::PreEditChanged( void*, Ecore_IMF_Context* imfContext, void* event_info )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::PreEditChanged\n" );
+
+  char* preEditString( NULL );
+  int cursorPosition( 0 );
+  Eina_List* attrs = NULL;
+  Eina_List* l = NULL;
+
+  Ecore_IMF_Preedit_Attr* attr;
+
+  // Retrieves attributes as well as the string the cursor position offset from start of pre-edit string.
+  // the attributes (attrs) is used in languages that use the soft arrows keys to insert characters into a current pre-edit string.
+  ecore_imf_context_preedit_string_with_attributes_get( imfContext, &preEditString, &attrs, &cursorPosition );
+
+  if ( attrs )
+  {
+    // iterate through the list of attributes getting the type, start and end position.
+    for ( l = attrs, (attr =  static_cast<Ecore_IMF_Preedit_Attr*>( eina_list_data_get(l) ) ); l; l = eina_list_next(l), ( attr = static_cast<Ecore_IMF_Preedit_Attr*>( eina_list_data_get(l) ) ))
+    {
+#ifdef DALI_PROFILE_UBUNTU
+      if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB3 ) // (Ecore_IMF)
+#else // DALI_PROFILE_UBUNTU
+      if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB4 ) // (Ecore_IMF)
+#endif // DALI_PROFILE_UBUNTU
+      {
+        // check first byte so know how many bytes a character is represented by as keyboard returns cursor position in bytes. Which is different for some languages.
+
+        size_t visualCharacterIndex = 0;
+        size_t byteIndex = 0;
+
+        // iterate through null terminated string checking each character's position against the given byte position ( attr->end_index ).
+        const char leadByte = preEditString[byteIndex];
+        while( leadByte != '\0' )
+        {
+          // attr->end_index is provided as a byte position not character and we need to know the character position.
+          const size_t currentSequenceLength = Utf8SequenceLength( leadByte ); // returns number of bytes used to represent character.
+          if ( byteIndex == attr->end_index )
+          {
+            cursorPosition = visualCharacterIndex;
+            break;
+            // end loop as found cursor position that matches byte position
+          }
+          else
+          {
+            byteIndex += currentSequenceLength; // jump to next character
+            visualCharacterIndex++;  // increment character count so we know our position for when we get a match
+          }
+
+          DALI_ASSERT_DEBUG( visualCharacterIndex < strlen( preEditString ));
+        }
+      }
+    }
+  }
+
+  if ( Dali::Adaptor::IsAvailable() )
+  {
+    Dali::ImfManager handle( this );
+    Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::PREEDIT, preEditString, cursorPosition, 0 );
+    Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
+
+    if ( callbackData.update )
+    {
+      SetCursorPosition( callbackData.cursorPosition );
+      SetSurroundingText( callbackData.currentText );
+
+      NotifyCursorPosition();
+    }
+
+    if ( callbackData.preeditResetRequired )
+    {
+      Reset();
+    }
+  }
+  free( preEditString );
 }
 
-void ImfManager::CommitReceived( void *, Ecore_IMF_Context *imfContext, void *event_info )
+void ImfManager::CommitReceived( void*, Ecore_IMF_Context* imfContext, void* event_info )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CommitReceived\n" );
+
+  if ( Dali::Adaptor::IsAvailable() )
+  {
+    const std::string keyString( static_cast<char*>( event_info ) );
+
+    Dali::ImfManager handle( this );
+    Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::COMMIT, keyString, 0, 0 );
+    Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
+
+    if( callbackData.update )
+    {
+      SetCursorPosition( callbackData.cursorPosition );
+      SetSurroundingText( callbackData.currentText );
+
+      NotifyCursorPosition();
+    }
+  }
 }
 
-Eina_Bool ImfManager::RetrieveSurrounding( void *data, Ecore_IMF_Context *imfContext, char** text, int* cursorPosition )
+/**
+ * Called when an IMF retrieve surround event is received.
+ * Here the IMF module wishes to know the string we are working with and where within the string the cursor is
+ * We need to signal the application to tell us this information.
+ */
+Eina_Bool ImfManager::RetrieveSurrounding( void* data, Ecore_IMF_Context* imfContext, char** text, int* cursorPosition )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::RetrieveSurrounding\n" );
+
+  Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::GETSURROUNDING, std::string(), 0, 0 );
+  Dali::ImfManager handle( this );
+  mEventSignal.Emit( handle, imfData );
+
+  if( text )
+  {
+    *text = strdup( mSurroundingText.c_str() );
+  }
+
+  if( cursorPosition )
+  {
+    *cursorPosition = mIMFCursorPosition;
+  }
+
   return EINA_TRUE;
 }
 
-void ImfManager::DeleteSurrounding( void *data, Ecore_IMF_Context *imfContext, void *event_info )
+/**
+ * Called when an IMF delete surrounding event is received.
+ * Here we tell the application that it should delete a certain range.
+ */
+void ImfManager::DeleteSurrounding( void* data, Ecore_IMF_Context* imfContext, void* event_info )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteSurrounding\n" );
+
+  if( Dali::Adaptor::IsAvailable() )
+  {
+    Ecore_IMF_Event_Delete_Surrounding* deleteSurroundingEvent = static_cast<Ecore_IMF_Event_Delete_Surrounding*>( event_info );
+
+    Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::DELETESURROUNDING, std::string(), deleteSurroundingEvent->offset, deleteSurroundingEvent->n_chars );
+    Dali::ImfManager handle( this );
+    mEventSignal.Emit( handle, imfData );
+  }
 }
 
 void ImfManager::NotifyCursorPosition()
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::NotifyCursorPosition\n" );
+
+  if( mIMFContext )
+  {
+    ecore_imf_context_cursor_position_set( mIMFContext, mIMFCursorPosition );
+  }
 }
 
 void ImfManager::SetCursorPosition( unsigned int cursorPosition )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetCursorPosition\n" );
+
   mIMFCursorPosition = static_cast<int>( cursorPosition );
 }
 
 unsigned int ImfManager::GetCursorPosition() const
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetCursorPosition\n" );
+
   return static_cast<unsigned int>( mIMFCursorPosition );
 }
 
 void ImfManager::SetSurroundingText( const std::string& text )
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetSurroundingText\n" );
+
   mSurroundingText = text;
 }
 
 const std::string& ImfManager::GetSurroundingText() const
 {
+  DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetSurroundingText\n" );
+
   return mSurroundingText;
 }
 
index ff5c61f..cace57a 100644 (file)
@@ -171,12 +171,19 @@ protected:
 
 private:
   /**
+   * Context created the first time and kept until deleted.
+   * @param[in] ecoreWlwin, The window is created by application.
+   */
+  void CreateContext( Ecore_Wl_Window *ecoreWlwin );
+
+  /**
    * @copydoc Dali::ImfManager::DeleteContext()
    */
   void DeleteContext();
 
 private:
   // Undefined
+  ImfManager( Ecore_Wl_Window *ecoreWlwin );
   ImfManager( const ImfManager& );
   ImfManager& operator=( ImfManager& );
 
index 3003364..02fb177 100644 (file)
@@ -46,12 +46,40 @@ namespace VirtualKeyboard
 
 Dali::InputMethod::ActionButton gActionButtonFunction = Dali::InputMethod::ACTION_DEFAULT;
 
+Ecore_IMF_Input_Panel_Return_Key_Type actionButtonMapping(Dali::InputMethod::ActionButton actionButton )
+{
+  switch( actionButton )
+  {
+    case InputMethod::ACTION_DEFAULT:     return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+    case InputMethod::ACTION_DONE:        return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE;
+    case InputMethod::ACTION_GO:          return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO;
+    case InputMethod::ACTION_JOIN:        return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_JOIN;
+    case InputMethod::ACTION_LOGIN:       return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_LOGIN;
+    case InputMethod::ACTION_NEXT:        return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_NEXT;
+    case InputMethod::ACTION_PREVIOUS:    return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+    case InputMethod::ACTION_SEARCH:      return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH;
+    case InputMethod::ACTION_SEND:        return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEND;
+    case InputMethod::ACTION_SIGNIN:      return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+    case InputMethod::ACTION_UNSPECIFIED: return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+    case InputMethod::ACTION_NONE:        return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+    default:                              return ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
+  }
+}
+
 void RotateTo(int angle)
 {
 }
 
 void SetReturnKeyType( const InputMethod::ActionButton type )
 {
+  Dali::ImfManager imfManager = ImfManager::Get(); // Create ImfManager instance (if required) when setting values
+  Ecore_IMF_Context* imfContext = ImfManager::GetImplementation( imfManager ).GetContext();
+
+  if( imfContext )
+  {
+    gActionButtonFunction = type;
+    ecore_imf_context_input_panel_return_key_type_set( imfContext, actionButtonMapping( type ) );
+  }
 }
 
 Dali::InputMethod::ActionButton GetReturnKeyType()
index e2ad5d6..32d0227 100644 (file)
@@ -67,6 +67,13 @@ struct Window::EventHandler
     mClientMessageHandler( NULL ),
     mEcoreWindow( 0 )
   {
+    // store ecore window handle
+    ECore::WindowRenderSurface* wlWindow( dynamic_cast< ECore::WindowRenderSurface * >( mWindow->mSurface ) );
+    if( wlWindow )
+    {
+      mEcoreWindow = wlWindow->GetWlWindow();
+    }
+    DALI_ASSERT_ALWAYS( mEcoreWindow != 0 && "There is no ecore Wl window");
   }
 
   /**