IMF manager clean up.
[platform/core/uifw/dali-adaptor.git] / adaptors / x11 / imf-manager-impl-x.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <imf-manager-impl.h>
20
21 // EXTERNAL INCLUDES
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>
25
26 // INTERNAL INCLUDES
27 #include <adaptor.h>
28 #include <window-render-surface.h>
29 #include <adaptor-impl.h>
30 #include <singleton-service-impl.h>
31 #include <virtual-keyboard-impl.h>
32
33 namespace Dali
34 {
35
36 namespace Internal
37 {
38
39 namespace Adaptor
40 {
41
42 namespace
43 {
44 #if defined(DEBUG_ENABLED)
45 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_IMF_MANAGER");
46 #endif
47
48 // Currently this code is internal to dali/dali/internal/event/text/utf8.h but should be made Public and used from there instead.
49 size_t Utf8SequenceLength(const unsigned char leadByte)
50 {
51   size_t length = 0;
52
53   if ((leadByte & 0x80) == 0 )          //ASCII character (lead bit zero)
54   {
55     length = 1;
56   }
57   else if (( leadByte & 0xe0 ) == 0xc0 ) //110x xxxx
58   {
59     length = 2;
60   }
61   else if (( leadByte & 0xf0 ) == 0xe0 ) //1110 xxxx
62   {
63     length = 3;
64   }
65   else if (( leadByte & 0xf8 ) == 0xf0 ) //1111 0xxx
66   {
67     length = 4;
68   }
69
70   return length;
71 }
72
73 // Static function calls used by ecore 'c' style callback registration
74 void Commit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
75 {
76   if ( data )
77   {
78     ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
79     imfManager->CommitReceived( data, imfContext, event_info );
80   }
81 }
82
83 void PreEdit( void *data, Ecore_IMF_Context *imfContext, void *event_info )
84 {
85   if ( data )
86   {
87     ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
88     imfManager->PreEditChanged( data, imfContext, event_info );
89   }
90 }
91
92 Eina_Bool ImfRetrieveSurrounding(void *data, Ecore_IMF_Context *imfContext, char** text, int* cursorPosition )
93 {
94   if ( data )
95   {
96     ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
97     return imfManager->RetrieveSurrounding( data, imfContext, text, cursorPosition );
98   }
99   else
100   {
101     return false;
102   }
103 }
104
105 /**
106  * Called when an IMF delete surrounding event is received.
107  * Here we tell the application that it should delete a certain range.
108  */
109 void ImfDeleteSurrounding( void *data, Ecore_IMF_Context *imfContext, void *event_info )
110 {
111   if ( data )
112   {
113     ImfManager* imfManager = reinterpret_cast< ImfManager* > ( data );
114     imfManager->DeleteSurrounding( data, imfContext, event_info );
115   }
116 }
117
118 BaseHandle Create()
119 {
120   return ImfManager::Get();
121 }
122
123 TypeRegistration IMF_MANAGER_TYPE( typeid(Dali::ImfManager), typeid(Dali::BaseHandle), Create );
124
125 } // unnamed namespace
126
127 bool ImfManager::IsAvailable()
128 {
129   bool available( false );
130
131   Dali::SingletonService service( SingletonService::Get() );
132   if ( service )
133   {
134     available = service.GetSingleton( typeid( Dali::ImfManager ) );
135   }
136
137   return available;
138 }
139
140 Dali::ImfManager ImfManager::Get()
141 {
142   Dali::ImfManager manager;
143
144   Dali::SingletonService service( SingletonService::Get() );
145   if ( service )
146   {
147     // Check whether the singleton is already created
148     Dali::BaseHandle handle = service.GetSingleton( typeid( Dali::ImfManager ) );
149     if( handle )
150     {
151       // If so, downcast the handle
152       manager = Dali::ImfManager( dynamic_cast< ImfManager* >( handle.GetObjectPtr() ) );
153     }
154     else if ( Adaptor::IsAvailable() )
155     {
156       // Create instance and register singleton only if the adaptor is available
157
158       Adaptor& adaptorImpl( Adaptor::GetImplementation( Adaptor::Get() ) );
159       Any nativeWindow = adaptorImpl.GetNativeWindowHandle();
160
161       // The Ecore_X_Window needs to use the ImfManager.
162       // Only when the render surface is window, we can get the Ecore_X_Window.
163       Ecore_X_Window ecoreXwin( AnyCast<Ecore_X_Window>(nativeWindow) );
164       if (ecoreXwin)
165       {
166         // If we fail to get Ecore_X_Window, we can't use the ImfManager correctly.
167         // Thus you have to call "ecore_imf_context_client_window_set" somewhere.
168         // In EvasPlugIn, this function is called in EvasPlugin::ConnectEcoreEvent().
169
170         manager = Dali::ImfManager( new ImfManager( ecoreXwin ) );
171         service.Register( typeid( manager ), manager );
172       }
173       else
174       {
175         DALI_LOG_ERROR("Failed to get native window handle");
176       }
177     }
178   }
179
180   return manager;
181 }
182
183 ImfManager::ImfManager( Ecore_X_Window ecoreXwin )
184 : mIMFContext(),
185   mIMFCursorPosition( 0 ),
186   mSurroundingText(),
187   mRestoreAfterFocusLost( false ),
188   mIdleCallbackConnected( false )
189 {
190   ecore_imf_init();
191   CreateContext( ecoreXwin );
192
193   ConnectCallbacks();
194   VirtualKeyboard::ConnectCallbacks( mIMFContext );
195 }
196
197 ImfManager::~ImfManager()
198 {
199   VirtualKeyboard::DisconnectCallbacks( mIMFContext );
200   DisconnectCallbacks();
201
202   DeleteContext();
203   ecore_imf_shutdown();
204 }
205
206 void ImfManager::CreateContext( Ecore_X_Window ecoreXwin )
207 {
208   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CreateContext\n" );
209
210   const char *contextId = ecore_imf_context_default_id_get();
211   if( contextId )
212   {
213     mIMFContext = ecore_imf_context_add( contextId );
214
215     if( mIMFContext )
216     {
217       if( ecoreXwin )
218       {
219         ecore_imf_context_client_window_set( mIMFContext, reinterpret_cast<void*>( ecoreXwin ) );
220       }
221     }
222     else
223     {
224       DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
225     }
226   }
227   else
228   {
229     DALI_LOG_WARNING("IMF Unable to get IMF Context\n");
230   }
231 }
232
233 void ImfManager::DeleteContext()
234 {
235   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteContext\n" );
236
237   if ( mIMFContext )
238   {
239     mIMFContext = NULL;
240   }
241 }
242
243 // Callbacks for predicitive text support.
244 void ImfManager::ConnectCallbacks()
245 {
246   if ( mIMFContext )
247   {
248     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::ConnectCallbacks\n" );
249
250     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit,    this );
251     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit,     this );
252     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding, this );
253
254     ecore_imf_context_retrieve_surrounding_callback_set( mIMFContext, ImfRetrieveSurrounding, this);
255   }
256 }
257
258 void ImfManager::DisconnectCallbacks()
259 {
260   if ( mIMFContext )
261   {
262     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DisconnectCallbacks\n" );
263
264     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit );
265     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit );
266     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding );
267
268     // We do not need to unset the retrieve surrounding callback.
269   }
270 }
271
272 void ImfManager::Activate()
273 {
274   // Reset mIdleCallbackConnected
275   mIdleCallbackConnected = false;
276
277   if ( mIMFContext )
278   {
279     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Activate\n" );
280
281     ecore_imf_context_focus_in( mIMFContext );
282
283     // emit keyboard activated signal
284     Dali::ImfManager handle( this );
285     mActivatedSignal.Emit( handle );
286   }
287 }
288
289 void ImfManager::Deactivate()
290 {
291   if( mIMFContext )
292   {
293     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Deactivate\n" );
294
295     Reset();
296     ecore_imf_context_focus_out( mIMFContext );
297   }
298
299   // Reset mIdleCallbackConnected
300   mIdleCallbackConnected = false;
301 }
302
303 void ImfManager::Reset()
304 {
305   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Reset\n" );
306
307   if ( mIMFContext )
308   {
309     ecore_imf_context_reset( mIMFContext );
310   }
311 }
312
313 Ecore_IMF_Context* ImfManager::GetContext()
314 {
315   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetContext\n" );
316
317   return mIMFContext;
318 }
319
320 bool ImfManager::RestoreAfterFocusLost() const
321 {
322   return mRestoreAfterFocusLost;
323 }
324
325 void ImfManager::SetRestoreAfterFocusLost( bool toggle )
326 {
327   mRestoreAfterFocusLost = toggle;
328 }
329
330 /**
331  * Called when an IMF Pre-Edit changed event is received.
332  * We are still predicting what the user is typing.  The latest string is what the IMF module thinks
333  * the user wants to type.
334  */
335 void ImfManager::PreEditChanged( void*, Ecore_IMF_Context* imfContext, void* event_info )
336 {
337   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::PreEditChanged\n" );
338
339   char* preEditString( NULL );
340   int cursorPosition( 0 );
341   Eina_List* attrs = NULL;
342   Eina_List* l = NULL;
343
344   Ecore_IMF_Preedit_Attr* attr;
345
346   // Retrieves attributes as well as the string the cursor position offset from start of pre-edit string.
347   // the attributes (attrs) is used in languages that use the soft arrows keys to insert characters into a current pre-edit string.
348   ecore_imf_context_preedit_string_with_attributes_get( imfContext, &preEditString, &attrs, &cursorPosition );
349
350   if ( attrs )
351   {
352     // iterate through the list of attributes getting the type, start and end position.
353     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) ) ))
354     {
355 #ifdef DALI_PROFILE_UBUNTU
356       if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB3 ) // (Ecore_IMF)
357 #else // DALI_PROFILE_UBUNTU
358       if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB4 ) // (Ecore_IMF)
359 #endif // DALI_PROFILE_UBUNTU
360       {
361         // 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.
362
363         size_t visualCharacterIndex = 0;
364         size_t byteIndex = 0;
365
366         // iterate through null terminated string checking each character's position against the given byte position ( attr->end_index ).
367         const char leadByte = preEditString[byteIndex];
368         while( leadByte != '\0' )
369         {
370           // attr->end_index is provided as a byte position not character and we need to know the character position.
371           const size_t currentSequenceLength = Utf8SequenceLength( leadByte ); // returns number of bytes used to represent character.
372           if ( byteIndex == attr->end_index )
373           {
374             cursorPosition = visualCharacterIndex;
375             break;
376             // end loop as found cursor position that matches byte position
377           }
378           else
379           {
380             byteIndex += currentSequenceLength; // jump to next character
381             visualCharacterIndex++;  // increment character count so we know our position for when we get a match
382           }
383
384           DALI_ASSERT_DEBUG( visualCharacterIndex < strlen( preEditString ));
385         }
386       }
387     }
388   }
389
390   if ( Dali::Adaptor::IsAvailable() )
391   {
392     Dali::ImfManager handle( this );
393     Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::PREEDIT, preEditString, cursorPosition, 0 );
394     Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
395
396     if ( callbackData.update )
397     {
398       SetCursorPosition( callbackData.cursorPosition );
399       SetSurroundingText( callbackData.currentText );
400
401       NotifyCursorPosition();
402     }
403
404     if ( callbackData.preeditResetRequired )
405     {
406       Reset();
407     }
408   }
409   free( preEditString );
410 }
411
412 void ImfManager::CommitReceived( void*, Ecore_IMF_Context* imfContext, void* event_info )
413 {
414   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CommitReceived\n" );
415
416   if ( Dali::Adaptor::IsAvailable() )
417   {
418     const std::string keyString( static_cast<char*>( event_info ) );
419
420     Dali::ImfManager handle( this );
421     Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::COMMIT, keyString, 0, 0 );
422     Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
423
424     if( callbackData.update )
425     {
426       SetCursorPosition( callbackData.cursorPosition );
427       SetSurroundingText( callbackData.currentText );
428
429       NotifyCursorPosition();
430     }
431   }
432 }
433
434 /**
435  * Called when an IMF retrieve surround event is received.
436  * Here the IMF module wishes to know the string we are working with and where within the string the cursor is
437  * We need to signal the application to tell us this information.
438  */
439 Eina_Bool ImfManager::RetrieveSurrounding( void* data, Ecore_IMF_Context* imfContext, char** text, int* cursorPosition )
440 {
441   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::RetrieveSurrounding\n" );
442
443   Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::GETSURROUNDING, std::string(), 0, 0 );
444   Dali::ImfManager handle( this );
445   mEventSignal.Emit( handle, imfData );
446
447   if( text )
448   {
449     *text = strdup( mSurroundingText.c_str() );
450   }
451
452   if( cursorPosition )
453   {
454     *cursorPosition = mIMFCursorPosition;
455   }
456
457   return EINA_TRUE;
458 }
459
460 /**
461  * Called when an IMF delete surrounding event is received.
462  * Here we tell the application that it should delete a certain range.
463  */
464 void ImfManager::DeleteSurrounding( void* data, Ecore_IMF_Context* imfContext, void* event_info )
465 {
466   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteSurrounding\n" );
467
468   if( Dali::Adaptor::IsAvailable() )
469   {
470     Ecore_IMF_Event_Delete_Surrounding* deleteSurroundingEvent = static_cast<Ecore_IMF_Event_Delete_Surrounding*>( event_info );
471
472     Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::DELETESURROUNDING, std::string(), deleteSurroundingEvent->offset, deleteSurroundingEvent->n_chars );
473     Dali::ImfManager handle( this );
474     mEventSignal.Emit( handle, imfData );
475   }
476 }
477
478 void ImfManager::NotifyCursorPosition()
479 {
480   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::NotifyCursorPosition\n" );
481
482   if( mIMFContext )
483   {
484     ecore_imf_context_cursor_position_set( mIMFContext, mIMFCursorPosition );
485   }
486 }
487
488 void ImfManager::SetCursorPosition( unsigned int cursorPosition )
489 {
490   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetCursorPosition\n" );
491
492   mIMFCursorPosition = static_cast<int>( cursorPosition );
493 }
494
495 unsigned int ImfManager::GetCursorPosition() const
496 {
497   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetCursorPosition\n" );
498
499   return static_cast<unsigned int>( mIMFCursorPosition );
500 }
501
502 void ImfManager::SetSurroundingText( const std::string& text )
503 {
504   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetSurroundingText\n" );
505
506   mSurroundingText = text;
507 }
508
509 const std::string& ImfManager::GetSurroundingText() const
510 {
511   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetSurroundingText\n" );
512
513   return mSurroundingText;
514 }
515
516 } // Adaptor
517
518 } // Internal
519
520 } // Dali