2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <imf-manager-impl.h>
22 #include <dali/public-api/events/key-event.h>
23 #include <dali/public-api/object/type-registry.h>
24 #include <dali/integration-api/debug.h>
27 #include <input-method-devel.h>
29 #include <locale-utils.h>
30 #include <window-render-surface.h>
31 #include <adaptor-impl.h>
32 #include <singleton-service-impl.h>
34 #define TOKEN_STRING(x) #x
36 Ecore_IMF_Input_Panel_Layout panelLayoutMap[] =
38 ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL,
39 ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER,
40 ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL,
41 ECORE_IMF_INPUT_PANEL_LAYOUT_URL,
42 ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER,
43 ECORE_IMF_INPUT_PANEL_LAYOUT_IP,
44 ECORE_IMF_INPUT_PANEL_LAYOUT_MONTH,
45 ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBERONLY,
46 ECORE_IMF_INPUT_PANEL_LAYOUT_HEX,
47 ECORE_IMF_INPUT_PANEL_LAYOUT_TERMINAL,
48 ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD,
49 ECORE_IMF_INPUT_PANEL_LAYOUT_DATETIME,
50 ECORE_IMF_INPUT_PANEL_LAYOUT_EMOTICON,
51 ECORE_IMF_INPUT_PANEL_LAYOUT_VOICE
54 Ecore_IMF_Autocapital_Type autoCapitalMap[] =
56 ECORE_IMF_AUTOCAPITAL_TYPE_NONE,
57 ECORE_IMF_AUTOCAPITAL_TYPE_WORD,
58 ECORE_IMF_AUTOCAPITAL_TYPE_SENTENCE,
59 ECORE_IMF_AUTOCAPITAL_TYPE_ALLCHARACTER,
62 Ecore_IMF_Input_Panel_Return_Key_Type returnKeyTypeMap[] =
64 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT,
65 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE,
66 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO,
67 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_JOIN,
68 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_LOGIN,
69 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_NEXT,
70 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH,
71 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEND,
72 ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SIGNIN
86 #if defined(DEBUG_ENABLED)
87 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMF_MANAGER");
90 // Currently this code is internal to dali/dali/internal/event/text/utf8.h but should be made Public and used from there instead.
91 size_t Utf8SequenceLength(const unsigned char leadByte)
95 if ((leadByte & 0x80) == 0 ) //ASCII character (lead bit zero)
99 else if (( leadByte & 0xe0 ) == 0xc0 ) //110x xxxx
103 else if (( leadByte & 0xf0 ) == 0xe0 ) //1110 xxxx
107 else if (( leadByte & 0xf8 ) == 0xf0 ) //1111 0xxx
115 // Static function calls used by ecore 'c' style callback registration
116 void Commit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
120 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
121 imfManager->CommitReceived( data, imfContext, event_info );
125 void PreEdit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
129 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
130 imfManager->PreEditChanged( data, imfContext, event_info );
134 Eina_Bool ImfRetrieveSurrounding(void *data, Ecore_IMF_Context *imfContext, char** text, int* cursorPosition )
138 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
139 return imfManager->RetrieveSurrounding( data, imfContext, text, cursorPosition );
147 void InputPanelStateChangeCallback( void* data, Ecore_IMF_Context* context, int value )
153 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
156 case ECORE_IMF_INPUT_PANEL_STATE_SHOW:
158 imfManager->StatusChangedSignal().Emit( true );
162 case ECORE_IMF_INPUT_PANEL_STATE_HIDE:
164 imfManager->StatusChangedSignal().Emit( false );
168 case ECORE_IMF_INPUT_PANEL_STATE_WILL_SHOW:
177 void InputPanelLanguageChangeCallback( void* data, Ecore_IMF_Context* context, int value )
183 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
184 // Emit the signal that the language has changed
185 imfManager->LanguageChangedSignal().Emit();
188 void InputPanelGeometryChangedCallback ( void *data, Ecore_IMF_Context *context, int value )
194 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
195 // Emit signal that the keyboard is resized
196 imfManager->ResizedSignal().Emit();
200 * Called when an IMF delete surrounding event is received.
201 * Here we tell the application that it should delete a certain range.
203 void ImfDeleteSurrounding( void *data, Ecore_IMF_Context *imfContext, void *event_info )
207 ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
208 imfManager->DeleteSurrounding( data, imfContext, event_info );
214 return ImfManager::Get();
217 TypeRegistration IMF_MANAGER_TYPE( typeid(Dali::ImfManager), typeid(Dali::BaseHandle), Create );
219 } // unnamed namespace
221 bool ImfManager::IsAvailable()
223 bool available( false );
225 Dali::SingletonService service( SingletonService::Get() );
228 available = service.GetSingleton( typeid( Dali::ImfManager ) );
234 Dali::ImfManager ImfManager::Get()
236 Dali::ImfManager manager;
238 Dali::SingletonService service( SingletonService::Get() );
241 // Check whether the singleton is already created
242 Dali::BaseHandle handle = service.GetSingleton( typeid( Dali::ImfManager ) );
245 // If so, downcast the handle
246 manager = Dali::ImfManager( dynamic_cast< ImfManager* >( handle.GetObjectPtr() ) );
248 else if ( Adaptor::IsAvailable() )
250 // Create instance and register singleton only if the adaptor is available
252 Adaptor& adaptorImpl( Adaptor::GetImplementation( Adaptor::Get() ) );
253 Any nativeWindow = adaptorImpl.GetNativeWindowHandle();
255 // The Ecore_Wl_Window needs to use the ImfManager.
256 // Only when the render surface is window, we can get the Ecore_Wl_Window.
257 Ecore_Wl_Window *ecoreWwin( AnyCast< Ecore_Wl_Window* >( nativeWindow ) );
260 // If we fail to get Ecore_Wl_Window, we can't use the ImfManager correctly.
261 // Thus you have to call "ecore_imf_context_client_window_set" somewhere.
262 // In EvasPlugIn, this function is called in EvasPlugin::ConnectEcoreEvent().
264 manager = Dali::ImfManager( new ImfManager( ecoreWwin ) );
265 service.Register( typeid( manager ), manager );
269 DALI_LOG_ERROR("Failed to get native window handle\n");
277 ImfManager::ImfManager( Ecore_Wl_Window *ecoreWlwin )
279 mIMFCursorPosition( 0 ),
281 mRestoreAfterFocusLost( false ),
282 mIdleCallbackConnected( false )
285 CreateContext( ecoreWlwin );
290 ImfManager::~ImfManager()
292 DisconnectCallbacks();
295 ecore_imf_shutdown();
299 void ImfManager::CreateContext( Ecore_Wl_Window *ecoreWlwin )
301 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CreateContext\n" );
303 const char *contextId = ecore_imf_context_default_id_get();
306 mIMFContext = ecore_imf_context_add( contextId );
312 ecore_imf_context_client_window_set( mIMFContext,
313 reinterpret_cast<void*>( ecore_wl_window_id_get(ecoreWlwin)) );
318 DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
323 DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
327 void ImfManager::DeleteContext()
329 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteContext\n" );
337 // Callbacks for predicitive text support.
338 void ImfManager::ConnectCallbacks()
342 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::ConnectCallbacks\n" );
344 ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreEdit, this );
345 ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_COMMIT, Commit, this );
346 ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding, this );
348 ecore_imf_context_input_panel_event_callback_add( mIMFContext, ECORE_IMF_INPUT_PANEL_STATE_EVENT, InputPanelStateChangeCallback, this );
349 ecore_imf_context_input_panel_event_callback_add( mIMFContext, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT, InputPanelLanguageChangeCallback, this );
350 ecore_imf_context_input_panel_event_callback_add( mIMFContext, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, InputPanelGeometryChangedCallback, this );
352 ecore_imf_context_retrieve_surrounding_callback_set( mIMFContext, ImfRetrieveSurrounding, this);
356 void ImfManager::DisconnectCallbacks()
360 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DisconnectCallbacks\n" );
362 ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, PreEdit );
363 ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_COMMIT, Commit );
364 ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding );
366 ecore_imf_context_input_panel_event_callback_del( mIMFContext, ECORE_IMF_INPUT_PANEL_STATE_EVENT, InputPanelStateChangeCallback );
367 ecore_imf_context_input_panel_event_callback_del( mIMFContext, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT, InputPanelLanguageChangeCallback );
368 ecore_imf_context_input_panel_event_callback_del( mIMFContext, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, InputPanelGeometryChangedCallback );
370 // We do not need to unset the retrieve surrounding callback.
374 void ImfManager::Activate()
376 // Reset mIdleCallbackConnected
377 mIdleCallbackConnected = false;
381 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Activate\n" );
383 ecore_imf_context_focus_in( mIMFContext );
385 // emit keyboard activated signal
386 Dali::ImfManager handle( this );
387 mActivatedSignal.Emit( handle );
391 void ImfManager::Deactivate()
395 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Deactivate\n" );
398 ecore_imf_context_focus_out( mIMFContext );
401 // Reset mIdleCallbackConnected
402 mIdleCallbackConnected = false;
405 void ImfManager::Reset()
407 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Reset\n" );
411 ecore_imf_context_reset( mIMFContext );
415 Ecore_IMF_Context* ImfManager::GetContext()
417 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetContext\n" );
422 bool ImfManager::RestoreAfterFocusLost() const
424 return mRestoreAfterFocusLost;
427 void ImfManager::SetRestoreAfterFocusLost( bool toggle )
429 mRestoreAfterFocusLost = toggle;
433 * Called when an IMF Pre-Edit changed event is received.
434 * We are still predicting what the user is typing. The latest string is what the IMF module thinks
435 * the user wants to type.
437 void ImfManager::PreEditChanged( void*, Ecore_IMF_Context* imfContext, void* event_info )
439 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::PreEditChanged\n" );
441 char* preEditString( NULL );
442 int cursorPosition( 0 );
443 Eina_List* attrs = NULL;
446 Ecore_IMF_Preedit_Attr* attr;
448 // Retrieves attributes as well as the string the cursor position offset from start of pre-edit string.
449 // the attributes (attrs) is used in languages that use the soft arrows keys to insert characters into a current pre-edit string.
450 ecore_imf_context_preedit_string_with_attributes_get( imfContext, &preEditString, &attrs, &cursorPosition );
454 // iterate through the list of attributes getting the type, start and end position.
455 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) ) ))
457 #ifdef DALI_PROFILE_UBUNTU
458 if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB3 ) // (Ecore_IMF)
459 #else // DALI_PROFILE_UBUNTU
460 if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB4 ) // (Ecore_IMF)
461 #endif // DALI_PROFILE_UBUNTU
463 // 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.
465 size_t visualCharacterIndex = 0;
466 size_t byteIndex = 0;
468 // iterate through null terminated string checking each character's position against the given byte position ( attr->end_index ).
469 const char leadByte = preEditString[byteIndex];
470 while( leadByte != '\0' )
472 // attr->end_index is provided as a byte position not character and we need to know the character position.
473 const size_t currentSequenceLength = Utf8SequenceLength( leadByte ); // returns number of bytes used to represent character.
474 if ( byteIndex == attr->end_index )
476 cursorPosition = visualCharacterIndex;
478 // end loop as found cursor position that matches byte position
482 byteIndex += currentSequenceLength; // jump to next character
483 visualCharacterIndex++; // increment character count so we know our position for when we get a match
486 DALI_ASSERT_DEBUG( visualCharacterIndex < strlen( preEditString ));
492 if ( Dali::Adaptor::IsAvailable() )
494 Dali::ImfManager handle( this );
495 Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::PREEDIT, preEditString, cursorPosition, 0 );
496 Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
498 if ( callbackData.update )
500 SetCursorPosition( callbackData.cursorPosition );
501 SetSurroundingText( callbackData.currentText );
503 NotifyCursorPosition();
506 if ( callbackData.preeditResetRequired )
511 free( preEditString );
514 void ImfManager::CommitReceived( void*, Ecore_IMF_Context* imfContext, void* event_info )
516 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CommitReceived\n" );
518 if ( Dali::Adaptor::IsAvailable() )
520 const std::string keyString( static_cast<char*>( event_info ) );
522 Dali::ImfManager handle( this );
523 Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::COMMIT, keyString, 0, 0 );
524 Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
526 if( callbackData.update )
528 SetCursorPosition( callbackData.cursorPosition );
529 SetSurroundingText( callbackData.currentText );
531 NotifyCursorPosition();
537 * Called when an IMF retrieve surround event is received.
538 * Here the IMF module wishes to know the string we are working with and where within the string the cursor is
539 * We need to signal the application to tell us this information.
541 Eina_Bool ImfManager::RetrieveSurrounding( void* data, Ecore_IMF_Context* imfContext, char** text, int* cursorPosition )
543 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::RetrieveSurrounding\n" );
545 Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::GETSURROUNDING, std::string(), 0, 0 );
546 Dali::ImfManager handle( this );
547 mEventSignal.Emit( handle, imfData );
551 *text = strdup( mSurroundingText.c_str() );
556 *cursorPosition = mIMFCursorPosition;
563 * Called when an IMF delete surrounding event is received.
564 * Here we tell the application that it should delete a certain range.
566 void ImfManager::DeleteSurrounding( void* data, Ecore_IMF_Context* imfContext, void* event_info )
568 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteSurrounding\n" );
570 if( Dali::Adaptor::IsAvailable() )
572 Ecore_IMF_Event_Delete_Surrounding* deleteSurroundingEvent = static_cast<Ecore_IMF_Event_Delete_Surrounding*>( event_info );
574 Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::DELETESURROUNDING, std::string(), deleteSurroundingEvent->offset, deleteSurroundingEvent->n_chars );
575 Dali::ImfManager handle( this );
576 mEventSignal.Emit( handle, imfData );
580 void ImfManager::NotifyCursorPosition()
582 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::NotifyCursorPosition\n" );
586 ecore_imf_context_cursor_position_set( mIMFContext, mIMFCursorPosition );
590 void ImfManager::SetCursorPosition( unsigned int cursorPosition )
592 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetCursorPosition\n" );
594 mIMFCursorPosition = static_cast<int>( cursorPosition );
597 unsigned int ImfManager::GetCursorPosition() const
599 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetCursorPosition\n" );
601 return static_cast<unsigned int>( mIMFCursorPosition );
604 void ImfManager::SetSurroundingText( const std::string& text )
606 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetSurroundingText\n" );
608 mSurroundingText = text;
611 const std::string& ImfManager::GetSurroundingText() const
613 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetSurroundingText\n" );
615 return mSurroundingText;
618 void ImfManager::NotifyTextInputMultiLine( bool multiLine )
620 Ecore_IMF_Input_Hints currentHint = ecore_imf_context_input_hint_get(mIMFContext);
621 ecore_imf_context_input_hint_set(mIMFContext, (Ecore_IMF_Input_Hints)(multiLine ?
622 (currentHint | ECORE_IMF_INPUT_HINT_MULTILINE) :
623 (currentHint & ~ECORE_IMF_INPUT_HINT_MULTILINE)));
626 Dali::ImfManager::TextDirection ImfManager::GetTextDirection()
628 Dali::ImfManager::TextDirection direction ( Dali::ImfManager::LeftToRight );
630 if ( ImfManager::IsAvailable() /* We do not want to create an instance of ImfManager */ )
634 char* locale( NULL );
635 ecore_imf_context_input_panel_language_locale_get( mIMFContext, &locale );
639 direction = Locale::GetTextDirection( std::string( locale ) );
647 Rect<int> ImfManager::GetInputMethodArea()
649 int xPos, yPos, width, height;
651 width = height = xPos = yPos = 0;
655 ecore_imf_context_input_panel_geometry_get( mIMFContext, &xPos, &yPos, &width, &height );
659 DALI_LOG_WARNING("VKB Unable to get IMF Context so GetSize unavailable\n");
660 // return 0 as real size unknown.
663 return Rect<int>(xPos,yPos,width,height);
666 void ImfManager::ApplyOptions( const InputMethodOptions& options )
668 using namespace Dali::InputMethod::Category;
672 if (mIMFContext == NULL)
674 DALI_LOG_WARNING("VKB Unable to excute ApplyOptions with Null ImfContext\n");
678 if ( mOptions.CompareAndSet(PANEL_LAYOUT, options, index) )
680 ecore_imf_context_input_panel_layout_set( mIMFContext, panelLayoutMap[index] );
682 if ( mOptions.CompareAndSet(AUTO_CAPITALISE, options, index) )
684 ecore_imf_context_autocapital_type_set( mIMFContext, autoCapitalMap[index] );
686 if ( mOptions.CompareAndSet(ACTION_BUTTON_TITLE, options, index) )
688 ecore_imf_context_input_panel_return_key_type_set( mIMFContext, returnKeyTypeMap[index] );
690 if ( mOptions.CompareAndSet(VARIATION, options, index) )
692 ecore_imf_context_input_panel_layout_variation_set( mIMFContext, index );