Implement IMF Manager additional functions
[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     ecore_imf_context_del( mIMFContext );
235     mIMFContext = NULL;
236   }
237 }
238
239 // Callbacks for predicitive text support.
240 void ImfManager::ConnectCallbacks()
241 {
242   if ( mIMFContext )
243   {
244     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::ConnectCallbacks\n" );
245
246     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit,    this );
247     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit,     this );
248     ecore_imf_context_event_callback_add( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding, this );
249
250     ecore_imf_context_retrieve_surrounding_callback_set( mIMFContext, ImfRetrieveSurrounding, this);
251   }
252 }
253
254 void ImfManager::DisconnectCallbacks()
255 {
256   if ( mIMFContext )
257   {
258     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DisconnectCallbacks\n" );
259
260     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_PREEDIT_CHANGED,    PreEdit );
261     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_COMMIT,             Commit );
262     ecore_imf_context_event_callback_del( mIMFContext, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, ImfDeleteSurrounding );
263
264     // We do not need to unset the retrieve surrounding callback.
265   }
266 }
267
268 void ImfManager::Activate()
269 {
270   // Reset mIdleCallbackConnected
271   mIdleCallbackConnected = false;
272
273   if ( mIMFContext )
274   {
275     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Activate\n" );
276
277     ecore_imf_context_focus_in( mIMFContext );
278
279     // emit keyboard activated signal
280     Dali::ImfManager handle( this );
281     mActivatedSignal.Emit( handle );
282   }
283 }
284
285 void ImfManager::Deactivate()
286 {
287   if( mIMFContext )
288   {
289     DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Deactivate\n" );
290
291     Reset();
292     ecore_imf_context_focus_out( mIMFContext );
293   }
294
295   // Reset mIdleCallbackConnected
296   mIdleCallbackConnected = false;
297 }
298
299 void ImfManager::Reset()
300 {
301   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::Reset\n" );
302
303   if ( mIMFContext )
304   {
305     ecore_imf_context_reset( mIMFContext );
306   }
307 }
308
309 Ecore_IMF_Context* ImfManager::GetContext()
310 {
311   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetContext\n" );
312
313   return mIMFContext;
314 }
315
316 bool ImfManager::RestoreAfterFocusLost() const
317 {
318   return mRestoreAfterFocusLost;
319 }
320
321 void ImfManager::SetRestoreAfterFocusLost( bool toggle )
322 {
323   mRestoreAfterFocusLost = toggle;
324 }
325
326 /**
327  * Called when an IMF Pre-Edit changed event is received.
328  * We are still predicting what the user is typing.  The latest string is what the IMF module thinks
329  * the user wants to type.
330  */
331 void ImfManager::PreEditChanged( void*, Ecore_IMF_Context* imfContext, void* event_info )
332 {
333   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::PreEditChanged\n" );
334
335   char* preEditString( NULL );
336   int cursorPosition( 0 );
337   Eina_List* attrs = NULL;
338   Eina_List* l = NULL;
339
340   Ecore_IMF_Preedit_Attr* attr;
341
342   // Retrieves attributes as well as the string the cursor position offset from start of pre-edit string.
343   // the attributes (attrs) is used in languages that use the soft arrows keys to insert characters into a current pre-edit string.
344   ecore_imf_context_preedit_string_with_attributes_get( imfContext, &preEditString, &attrs, &cursorPosition );
345
346   if ( attrs )
347   {
348     // iterate through the list of attributes getting the type, start and end position.
349     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) ) ))
350     {
351 #ifdef DALI_PROFILE_UBUNTU
352       if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB3 ) // (Ecore_IMF)
353 #else // DALI_PROFILE_UBUNTU
354       if ( attr->preedit_type == ECORE_IMF_PREEDIT_TYPE_SUB4 ) // (Ecore_IMF)
355 #endif // DALI_PROFILE_UBUNTU
356       {
357         // 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.
358
359         size_t visualCharacterIndex = 0;
360         size_t byteIndex = 0;
361
362         // iterate through null terminated string checking each character's position against the given byte position ( attr->end_index ).
363         const char leadByte = preEditString[byteIndex];
364         while( leadByte != '\0' )
365         {
366           // attr->end_index is provided as a byte position not character and we need to know the character position.
367           const size_t currentSequenceLength = Utf8SequenceLength( leadByte ); // returns number of bytes used to represent character.
368           if ( byteIndex == attr->end_index )
369           {
370             cursorPosition = visualCharacterIndex;
371             break;
372             // end loop as found cursor position that matches byte position
373           }
374           else
375           {
376             byteIndex += currentSequenceLength; // jump to next character
377             visualCharacterIndex++;  // increment character count so we know our position for when we get a match
378           }
379
380           DALI_ASSERT_DEBUG( visualCharacterIndex < strlen( preEditString ));
381         }
382       }
383     }
384   }
385
386   if ( Dali::Adaptor::IsAvailable() )
387   {
388     Dali::ImfManager handle( this );
389     Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::PREEDIT, preEditString, cursorPosition, 0 );
390     Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
391
392     if( callbackData.update )
393     {
394       mIMFCursorPosition = static_cast<int>( callbackData.cursorPosition );
395
396       NotifyCursorPosition();
397     }
398
399     if( callbackData.preeditResetRequired )
400     {
401       Reset();
402     }
403   }
404   free( preEditString );
405 }
406
407 void ImfManager::CommitReceived( void*, Ecore_IMF_Context* imfContext, void* event_info )
408 {
409   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::CommitReceived\n" );
410
411   if ( Dali::Adaptor::IsAvailable() )
412   {
413     const std::string keyString( static_cast<char*>( event_info ) );
414
415     Dali::ImfManager handle( this );
416     Dali::ImfManager::ImfEventData imfEventData( Dali::ImfManager::COMMIT, keyString, 0, 0 );
417     Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfEventData );
418
419     if( callbackData.update )
420     {
421       mIMFCursorPosition = static_cast<int>( callbackData.cursorPosition );
422
423       NotifyCursorPosition();
424     }
425   }
426 }
427
428 /**
429  * Called when an IMF retrieve surround event is received.
430  * Here the IMF module wishes to know the string we are working with and where within the string the cursor is
431  * We need to signal the application to tell us this information.
432  */
433 Eina_Bool ImfManager::RetrieveSurrounding( void* data, Ecore_IMF_Context* imfContext, char** text, int* cursorPosition )
434 {
435   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::RetrieveSurrounding\n" );
436
437   Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::GETSURROUNDING, std::string(), 0, 0 );
438   Dali::ImfManager handle( this );
439   Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfData );
440
441   if( callbackData.update )
442   {
443     if( text )
444     {
445       *text = strdup( callbackData.currentText.c_str() );
446     }
447
448     if( cursorPosition )
449     {
450       mIMFCursorPosition = static_cast<int>( callbackData.cursorPosition );
451       *cursorPosition = mIMFCursorPosition;
452     }
453   }
454
455   return EINA_TRUE;
456 }
457
458 /**
459  * Called when an IMF delete surrounding event is received.
460  * Here we tell the application that it should delete a certain range.
461  */
462 void ImfManager::DeleteSurrounding( void* data, Ecore_IMF_Context* imfContext, void* event_info )
463 {
464   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::DeleteSurrounding\n" );
465
466   if( Dali::Adaptor::IsAvailable() )
467   {
468     Ecore_IMF_Event_Delete_Surrounding* deleteSurroundingEvent = static_cast<Ecore_IMF_Event_Delete_Surrounding*>( event_info );
469
470     Dali::ImfManager::ImfEventData imfData( Dali::ImfManager::DELETESURROUNDING, std::string(), deleteSurroundingEvent->offset, deleteSurroundingEvent->n_chars );
471     Dali::ImfManager handle( this );
472     Dali::ImfManager::ImfCallbackData callbackData = mEventSignal.Emit( handle, imfData );
473
474     if( callbackData.update )
475     {
476       mIMFCursorPosition = static_cast<int>( callbackData.cursorPosition );
477
478       NotifyCursorPosition();
479     }
480   }
481 }
482
483 void ImfManager::NotifyCursorPosition()
484 {
485   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::NotifyCursorPosition\n" );
486
487   if( mIMFContext )
488   {
489     ecore_imf_context_cursor_position_set( mIMFContext, mIMFCursorPosition );
490   }
491 }
492
493 void ImfManager::SetCursorPosition( unsigned int cursorPosition )
494 {
495   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetCursorPosition\n" );
496
497   mIMFCursorPosition = static_cast<int>( cursorPosition );
498 }
499
500 unsigned int ImfManager::GetCursorPosition() const
501 {
502   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetCursorPosition\n" );
503
504   return static_cast<unsigned int>( mIMFCursorPosition );
505 }
506
507 void ImfManager::SetSurroundingText( const std::string& text )
508 {
509   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetSurroundingText\n" );
510
511   mSurroundingText = text;
512 }
513
514 const std::string& ImfManager::GetSurroundingText() const
515 {
516   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetSurroundingText\n" );
517
518   return mSurroundingText;
519 }
520
521 void ImfManager::NotifyTextInputMultiLine( bool multiLine )
522 {
523 }
524
525 Dali::ImfManager::TextDirection ImfManager::GetTextDirection()
526 {
527   Dali::ImfManager::TextDirection direction ( Dali::ImfManager::LeftToRight );
528
529   if ( ImfManager::IsAvailable() /* We do not want to create an instance of ImfManager */ )
530   {
531     if ( mIMFContext )
532     {
533       char* locale( NULL );
534       ecore_imf_context_input_panel_language_locale_get( mIMFContext, &locale );
535
536       if ( locale )
537       {
538         direction = Locale::GetTextDirection( std::string( locale ) );
539         free( locale );
540       }
541     }
542   }
543   return direction;
544 }
545
546 Rect<int> ImfManager::GetInputMethodArea()
547 {
548   int xPos, yPos, width, height;
549
550   width = height = xPos = yPos = 0;
551
552   if( mIMFContext )
553   {
554     ecore_imf_context_input_panel_geometry_get( mIMFContext, &xPos, &yPos, &width, &height );
555   }
556   else
557   {
558     DALI_LOG_WARNING("VKB Unable to get IMF Context so GetSize unavailable\n");
559   }
560
561   return Rect<int>(xPos,yPos,width,height);
562 }
563
564 void ImfManager::ApplyOptions( const InputMethodOptions& options )
565 {
566   using namespace Dali::InputMethod::Category;
567
568   int index;
569
570   if (mIMFContext == NULL)
571   {
572     DALI_LOG_WARNING("VKB Unable to excute ApplyOptions with Null ImfContext\n");
573     return;
574   }
575
576   if ( mOptions.CompareAndSet(PANEL_LAYOUT, options, index) )
577   {
578   }
579   if ( mOptions.CompareAndSet(AUTO_CAPITALISE, options, index) )
580   {
581   }
582   if ( mOptions.CompareAndSet(ACTION_BUTTON_TITLE, options, index) )
583   {
584   }
585   if ( mOptions.CompareAndSet(VARIATION, options, index) )
586   {
587   }
588 }
589
590 void ImfManager::SetInputPanelUserData( const std::string& data )
591 {
592   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetInputPanelUserData\n" );
593
594   if( mIMFContext )
595   {
596     int length = data.length();
597     ecore_imf_context_input_panel_imdata_set( mIMFContext, &data, length );
598   }
599 }
600
601 void ImfManager::GetInputPanelUserData( std::string& data )
602 {
603   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetInputPanelUserData\n" );
604
605   if( mIMFContext )
606   {
607     int* length = NULL;
608     ecore_imf_context_input_panel_imdata_get( mIMFContext, &data, length );
609   }
610 }
611
612 Dali::ImfManager::State ImfManager::GetInputPanelState()
613 {
614   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::GetInputPanelState\n" );
615
616   if( mIMFContext )
617   {
618     int value;
619     value = ecore_imf_context_input_panel_state_get( mIMFContext );
620
621     switch (value)
622     {
623       case ECORE_IMF_INPUT_PANEL_STATE_SHOW:
624       {
625         return Dali::ImfManager::SHOW;
626         break;
627       }
628
629       case ECORE_IMF_INPUT_PANEL_STATE_HIDE:
630       {
631         return Dali::ImfManager::HIDE;
632         break;
633       }
634
635       case ECORE_IMF_INPUT_PANEL_STATE_WILL_SHOW:
636       {
637         return Dali::ImfManager::WILL_SHOW;
638         break;
639       }
640
641       default:
642       {
643         return Dali::ImfManager::DEFAULT;
644       }
645     }
646   }
647   return Dali::ImfManager::DEFAULT;
648 }
649
650 void ImfManager::SetReturnKeyState( bool visible )
651 {
652   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::SetReturnKeyState\n" );
653
654   if( mIMFContext )
655   {
656     ecore_imf_context_input_panel_return_key_disabled_set( mIMFContext, !visible );
657   }
658 }
659
660 void ImfManager::AutoEnableInputPanel( bool enabled )
661 {
662   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::AutoEnableInputPanel\n" );
663
664   if( mIMFContext )
665   {
666     ecore_imf_context_input_panel_enabled_set( mIMFContext, enabled );
667   }
668 }
669
670 void ImfManager::ShowInputPanel()
671 {
672   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::ShowInputPanel\n" );
673
674   if( mIMFContext )
675   {
676     ecore_imf_context_input_panel_show( mIMFContext );
677   }
678 }
679
680 void ImfManager::HideInputPanel()
681 {
682   DALI_LOG_INFO( gLogFilter, Debug::General, "ImfManager::HideInputPanel\n" );
683
684   if( mIMFContext )
685   {
686     ecore_imf_context_focus_out( mIMFContext );
687     ecore_imf_context_input_panel_hide( mIMFContext );
688   }
689 }
690
691 } // Adaptor
692
693 } // Internal
694
695 } // Dali