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