c1236a64a168c7a19056086ab2cdfd81950ee93d
[platform/framework/web/chromium-efl.git] / tizen_src / chromium_impl / ui / ozone / platform / efl / im_context_efl.cc
1 // Copyright 2014 Samsung Electronics. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/ozone/platform/efl/im_context_efl.h"
6
7 #include <Ecore_Evas.h>
8 #include <Ecore_IMF_Evas.h>
9
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "content/public/browser/render_widget_host_helper.h"
13 #include "tizen/system_info.h"
14 #include "ui/base/ime/ime_text_span.h"
15 #include "ui/events/event.h"
16 #include "ui/ozone/platform/efl/efl_event_handler.h"
17 #include "ui/ozone/platform/efl/efl_platform_event_source.h"
18 #include "ui/ozone/platform/efl/efl_window.h"
19
20 #if BUILDFLAG(IS_TIZEN_TV)
21 #include "third_party/blink/public/platform/web_application_type.h"
22 #endif
23
24 #ifdef IM_CTX_DEBUG
25 #define IM_CTX_LOG_CHANNEL LOG(ERROR)
26 #else
27 #define IM_CTX_LOG_CHANNEL LOG(INFO)
28 #endif
29
30 #define IM_CTX_LOG IM_CTX_LOG_CHANNEL << "## IMCTX ## " << __FUNCTION__ << " "
31
32 namespace {
33
34 Ecore_IMF_Context* CreateIMFContext(Evas* evas) {
35   IM_CTX_LOG;
36   const char* default_context_id = ecore_imf_context_default_id_get();
37   if (!default_context_id) {
38     LOG(ERROR) << "no default context id";
39     return NULL;
40   }
41   Ecore_IMF_Context* context = ecore_imf_context_add(default_context_id);
42   if (!context) {
43     LOG(ERROR) << "cannot create context";
44     return NULL;
45   }
46
47   Ecore_Window window = ecore_evas_window_get(ecore_evas_ecore_evas_get(evas));
48   ecore_imf_context_client_window_set(context, reinterpret_cast<void*>(window));
49   ecore_imf_context_client_canvas_set(context, evas);
50
51   return context;
52 }
53
54 Eina_Bool IsIMFVisible(Ecore_IMF_Context* context_) {
55   return ecore_imf_context_input_panel_state_get(context_) ==
56          ECORE_IMF_INPUT_PANEL_STATE_SHOW;
57 }
58
59 }  // namespace
60
61 namespace ui {
62
63 // static
64 std::unique_ptr<IMContextEfl> IMContextEfl::Create(EflWindow* window) {
65   Ecore_IMF_Context* context = CreateIMFContext(window->evas());
66   if (!context)
67     return nullptr;
68
69   return std::make_unique<IMContextEfl>(context, window);
70 }
71
72 IMContextEfl::IMContextEfl(Ecore_IMF_Context* context, EflWindow* window)
73     : context_(context), window_(window) {
74   IM_CTX_LOG;
75   InitializeIMFContext();
76 }
77
78 void IMContextEfl::InitializeIMFContext() {
79   if (IsTvProfile()) {
80     const char* custom_conformant_enabled = "conformant:custom,enabled";
81     ecore_imf_context_input_panel_imdata_set(
82         context_, custom_conformant_enabled, strlen(custom_conformant_enabled));
83   }
84
85   ecore_imf_context_input_panel_enabled_set(context_, false);
86   ecore_imf_context_use_preedit_set(context_, false);
87   ecore_imf_context_event_callback_add(context_, ECORE_IMF_CALLBACK_COMMIT,
88                                        &IMFCommitCallback, this);
89   ecore_imf_context_event_callback_add(context_,
90                                        ECORE_IMF_CALLBACK_DELETE_SURROUNDING,
91                                        &IMFDeleteSurroundingCallback, this);
92   ecore_imf_context_event_callback_add(context_,
93                                        ECORE_IMF_CALLBACK_PREEDIT_CHANGED,
94                                        &IMFPreeditChangedCallback, this);
95   ecore_imf_context_event_callback_add(
96       context_, ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND,
97       &IMFTransactionPrivateCommandSendCallback, this);
98   ecore_imf_context_input_panel_event_callback_add(
99       context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT,
100       &IMFInputPanelStateChangedCallback, this);
101   ecore_imf_context_input_panel_event_callback_add(
102       context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT,
103       &IMFInputPanelGeometryChangedCallback, this);
104   ecore_imf_context_input_panel_event_callback_add(
105       context_, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT,
106       &IMFCandidatePanelStateChangedCallback, this);
107   ecore_imf_context_input_panel_event_callback_add(
108       context_, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT,
109       &IMFCandidatePanelGeometryChangedCallback, this);
110   ecore_imf_context_input_panel_event_callback_add(
111       context_, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT,
112       &IMFCandidatePanelLanguageChangedCallback, this);
113   ecore_imf_context_retrieve_surrounding_callback_set(
114       context_, &IMFRetrieveSurroundingCallback, this);
115 }
116
117 IMContextEfl::~IMContextEfl() {
118   ecore_imf_context_event_callback_del(context_, ECORE_IMF_CALLBACK_COMMIT,
119                                        &IMFCommitCallback);
120   ecore_imf_context_event_callback_del(context_,
121                                        ECORE_IMF_CALLBACK_DELETE_SURROUNDING,
122                                        &IMFDeleteSurroundingCallback);
123   ecore_imf_context_event_callback_del(
124       context_, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, &IMFPreeditChangedCallback);
125   ecore_imf_context_event_callback_del(
126       context_, ECORE_IMF_CALLBACK_PRIVATE_COMMAND_SEND,
127       &IMFTransactionPrivateCommandSendCallback);
128   ecore_imf_context_input_panel_event_callback_del(
129       context_, ECORE_IMF_INPUT_PANEL_STATE_EVENT,
130       &IMFInputPanelStateChangedCallback);
131   ecore_imf_context_input_panel_event_callback_del(
132       context_, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT,
133       &IMFInputPanelGeometryChangedCallback);
134   ecore_imf_context_input_panel_event_callback_del(
135       context_, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT,
136       &IMFCandidatePanelStateChangedCallback);
137   ecore_imf_context_input_panel_event_callback_del(
138       context_, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT,
139       &IMFCandidatePanelGeometryChangedCallback);
140   ecore_imf_context_input_panel_event_callback_del(
141       context_, ECORE_IMF_INPUT_PANEL_LANGUAGE_EVENT,
142       &IMFCandidatePanelLanguageChangedCallback);
143   ecore_imf_context_del(context_);
144 }
145
146 void IMContextEfl::HandleKeyDownEvent(const Evas_Event_Key_Down* event,
147                                       bool* was_filtered) {
148   Ecore_IMF_Event im_event;
149   ecore_imf_evas_event_key_down_wrap(const_cast<Evas_Event_Key_Down*>(event),
150                                      &im_event.key_down);
151   *was_filtered = ecore_imf_context_filter_event(
152       context_, ECORE_IMF_EVENT_KEY_DOWN, &im_event);
153   is_keyevent_processing_ = true;
154 }
155
156 void IMContextEfl::HandleKeyUpEvent(const Evas_Event_Key_Up* event,
157                                     bool* was_filtered) {
158   Ecore_IMF_Event im_event;
159   ecore_imf_evas_event_key_up_wrap(const_cast<Evas_Event_Key_Up*>(event),
160                                    &im_event.key_up);
161   *was_filtered = ecore_imf_context_filter_event(
162       context_, ECORE_IMF_EVENT_KEY_UP, &im_event);
163 }
164
165 void IMContextEfl::UpdateInputMethodType(TextInputType type,
166                                          TextInputMode mode,
167                                          bool can_compose_inline
168 #if BUILDFLAG(IS_TIZEN_TV)
169                                          ,
170                                          int password_input_minlength,
171                                          int input_maxlength
172 #endif
173 ) {
174   if (current_type_ == type && current_mode_ == mode &&
175       can_compose_inline_ == can_compose_inline
176 #if BUILDFLAG(IS_TIZEN_TV)
177       && password_input_minlength_ == password_input_minlength &&
178       input_maxlength_ == input_maxlength
179 #endif
180   ) {
181     return;
182   }
183
184 #if BUILDFLAG(IS_TIZEN_TV)
185   password_input_minlength_ = password_input_minlength;
186   input_maxlength_ = input_maxlength;
187 #endif
188
189   Ecore_IMF_Input_Panel_Layout layout;
190   Ecore_IMF_Input_Panel_Return_Key_Type return_key_type =
191       ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
192   Ecore_IMF_Autocapital_Type cap_type = ECORE_IMF_AUTOCAPITAL_TYPE_NONE;
193   bool allow_prediction = true;
194   bool is_multi_line __attribute__((unused)) = false;
195   bool disable_done_key = false;
196
197   return_key_type = is_in_form_tag_
198                         ? ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO
199                         : ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE;
200
201   switch (type) {
202     case TEXT_INPUT_TYPE_TEXT:
203       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
204       break;
205     case TEXT_INPUT_TYPE_PASSWORD:
206       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD;
207       allow_prediction = false;
208       break;
209     case TEXT_INPUT_TYPE_SEARCH:
210       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
211       return_key_type = ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH;
212       break;
213     case TEXT_INPUT_TYPE_EMAIL:
214       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL;
215       break;
216     case TEXT_INPUT_TYPE_NUMBER:
217       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER;
218       break;
219     case TEXT_INPUT_TYPE_TELEPHONE:
220       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER;
221       break;
222     case TEXT_INPUT_TYPE_URL:
223       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL;
224       break;
225     case TEXT_INPUT_TYPE_MONTH:
226       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_MONTH;
227       break;
228     case TEXT_INPUT_TYPE_TEXT_AREA:
229     case TEXT_INPUT_TYPE_CONTENT_EDITABLE:
230       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
231       cap_type = ECORE_IMF_AUTOCAPITAL_TYPE_SENTENCE;
232       return_key_type = ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT;
233       is_multi_line = true;
234       break;
235
236     // No direct mapping to Ecore_IMF API, use simple text layout.
237     case TEXT_INPUT_TYPE_DATE:
238     case TEXT_INPUT_TYPE_DATE_TIME:
239     case TEXT_INPUT_TYPE_DATE_TIME_LOCAL:
240     case TEXT_INPUT_TYPE_TIME:
241     case TEXT_INPUT_TYPE_WEEK:
242     case TEXT_INPUT_TYPE_DATE_TIME_FIELD:
243       layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
244       break;
245
246     case TEXT_INPUT_TYPE_NONE:
247       return;
248
249     default:
250       NOTREACHED();
251       return;
252   }
253
254 #if BUILDFLAG(IS_TIZEN_TV)
255   // 2017 WebBrowser App want no recommended list same as 2015, 2016.
256   if (blink::IsWebBrowser())
257     allow_prediction = false;
258
259   // Always enable "Up" and "Down" key
260   // Set IMEUp=2, IMEDown=2 will always enable the 'up' and 'down' arrow key
261   std::string im_data("IMEUp=2&IMEDown=2");
262
263   if (allow_prediction && is_get_lookup_table_from_app_)
264     im_data.append("&layouttype=1");
265
266   // set input max length by entrylimit
267   if (input_maxlength_ != DEFAULT_MAX_LENGTH && input_maxlength_ != -1)
268     im_data.append("&entrylimit=" + std::to_string(input_maxlength_));
269
270   ecore_imf_context_input_panel_imdata_set(context_, im_data.c_str(),
271                                            im_data.length());
272
273   // For password input type,disable "Done" key when inputed text not meet
274   // minlength
275   if (TEXT_INPUT_TYPE_PASSWORD == type &&
276       surrounding_text_length_ < password_input_minlength_) {
277     LOG(INFO) << "surrounding_text_length_:" << surrounding_text_length_
278               << ",password_input_minlength_:" << password_input_minlength_
279               << ",disable done key";
280     disable_done_key = true;
281   }
282 #endif
283
284   Ecore_IMF_Input_Mode imf_mode;
285   switch (mode) {
286     case TEXT_INPUT_MODE_NUMERIC:
287       imf_mode = ECORE_IMF_INPUT_MODE_NUMERIC;
288       break;
289     default:
290       imf_mode = ECORE_IMF_INPUT_MODE_ALPHA;
291   }
292
293   ecore_imf_context_input_panel_layout_set(context_, layout);
294   ecore_imf_context_input_mode_set(context_, imf_mode);
295   ecore_imf_context_input_panel_return_key_type_set(context_, return_key_type);
296   ecore_imf_context_autocapital_type_set(context_, cap_type);
297   ecore_imf_context_prediction_allow_set(context_, allow_prediction);
298   ecore_imf_context_input_panel_return_key_disabled_set(context_,
299                                                         disable_done_key);
300
301 #if BUILDFLAG(IS_TIZEN)
302   Ecore_IMF_Input_Hints hints;
303   if (is_multi_line) {
304     hints = static_cast<Ecore_IMF_Input_Hints>(
305         ecore_imf_context_input_hint_get(context_) |
306         ECORE_IMF_INPUT_HINT_MULTILINE);
307   } else {
308     hints = static_cast<Ecore_IMF_Input_Hints>(
309         ecore_imf_context_input_hint_get(context_) &
310         ~ECORE_IMF_INPUT_HINT_MULTILINE);
311   }
312   ecore_imf_context_input_hint_set(context_, hints);
313 #endif
314
315   // If the focused element supports inline rendering of composition text,
316   // we receive and send related events to it. Otherwise, the events related
317   // to the updates of composition text are directed to the candidate window.
318   ecore_imf_context_use_preedit_set(context_, can_compose_inline);
319
320   current_mode_ = mode;
321 }
322
323 bool IMContextEfl::ShouldHandleImmediately(const char* key) {
324   // Do not forward keyevent now if there is fake key event
325   // handling at the moment to preserve orders of events as in Webkit.
326   // Some keys should be processed after composition text is
327   // released.
328   // So some keys are pushed to KeyDownEventQueue first
329   // then it will be porcessed after composition text is processed.
330   return (
331       (IsPreeditQueueEmpty() || IsKeyUpQueueEmpty()) &&
332       !(!IsCommitQueueEmpty() &&
333         ((IsTvProfile() &&
334           (!strcmp(key, "Select") || !strcmp(key, "Cancel"))) ||
335          !strcmp(key, "Down") || !strcmp(key, "Up") || !strcmp(key, "Right") ||
336          !strcmp(key, "Left") || !strcmp(key, "space") || !strcmp(key, "Tab") ||
337          !strcmp(key, "Return"))));
338 }
339
340 void IMContextEfl::UpdateInputMethodState(TextInputType type,
341                                           bool can_compose_inline,
342                                           bool show_if_needed
343 #if BUILDFLAG(IS_TIZEN_TV)
344                                           ,
345                                           int password_input_minlength,
346                                           int input_maxlength
347 #endif
348 ) {
349 #if BUILDFLAG(IS_TIZEN_TV)
350   if (current_type_ != type || can_compose_inline_ != can_compose_inline ||
351       password_input_minlength_ != password_input_minlength ||
352       input_maxlength_ != input_maxlength) {
353     UpdateInputMethodType(type, TEXT_INPUT_MODE_DEFAULT, can_compose_inline,
354                           password_input_minlength, input_maxlength);
355 #else
356   if (current_type_ != type || can_compose_inline_ != can_compose_inline) {
357     UpdateInputMethodType(type, TEXT_INPUT_MODE_DEFAULT, can_compose_inline);
358 #endif
359   }
360
361   current_type_ = type;
362   can_compose_inline_ = can_compose_inline;
363
364   bool focus_in = type != TEXT_INPUT_TYPE_NONE;
365   if (focus_in == is_focused_ && (!show_if_needed || IsVisible()))
366     return;
367
368   if (focus_in && show_if_needed) {
369     ShowPanel();
370   } else if (focus_in)
371     OnFocusIn();
372   else if (IsVisible()) {
373     HidePanel();
374   } else {
375     OnFocusOut();
376   }
377 }
378
379 void IMContextEfl::ShowPanel() {
380   LOG(INFO) << "Show Input Panel!";
381   is_showing_ = is_focused_ = true;
382   ecore_imf_context_focus_in(context_);
383   ecore_imf_context_input_panel_show(context_);
384 }
385
386 void IMContextEfl::HidePanel() {
387   LOG(INFO) << "Hide Input Panel!";
388   is_showing_ = is_focused_ = false;
389   ecore_imf_context_focus_out(context_);
390   ecore_imf_context_input_panel_hide(context_);
391 }
392
393 void IMContextEfl::OnFocusIn() {
394   if (current_type_ == ui::TEXT_INPUT_TYPE_NONE)
395     return;
396
397   LOG(INFO) << "IME Focus In";
398   ecore_imf_context_focus_in(context_);
399   is_focused_ = true;
400 }
401
402 void IMContextEfl::OnFocusOut() {
403   LOG(INFO) << "IME Focus Out";
404   is_focused_ = false;
405
406   CancelComposition();
407   ecore_imf_context_focus_out(context_);
408 }
409
410 void IMContextEfl::ResetIMFContext() {
411   is_ime_ctx_reset_ = true;
412   ecore_imf_context_reset(context_);
413   is_ime_ctx_reset_ = false;
414 }
415
416 void IMContextEfl::CancelComposition() {
417   ClearQueues();
418   ResetIMFContext();
419
420   if (composition_.text.length() > 0) {
421     std::u16string empty;
422     ConfirmComposition(empty);
423     composition_.text = u"";
424   }
425 }
426
427 void IMContextEfl::SetIsInFormTag(bool is_in_form_tag) {
428   is_in_form_tag_ = is_in_form_tag;
429   // TODO: workaround on tizen v3.0 platform issue
430   // Even if virtual keyboard is shown,
431   // the API 'ecore_imf_context_input_panel_state_get()'
432   // returns '1' which means the keyboard is not shown.
433   // It makes the 'HidePanel()' unreachable.
434   if (!IsVisible())
435     return;
436
437   if (ecore_imf_context_input_panel_return_key_type_get(context_) ==
438           ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_SEARCH ||
439       current_type_ == TEXT_INPUT_TYPE_TEXT_AREA) {
440     return;
441   }
442
443   if (is_in_form_tag_) {
444     ecore_imf_context_input_panel_return_key_type_set(
445         context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_GO);
446   } else {
447     if (current_type_ == TEXT_INPUT_TYPE_TEXT ||
448         current_type_ == TEXT_INPUT_TYPE_NUMBER ||
449         current_type_ == TEXT_INPUT_TYPE_TELEPHONE ||
450         current_type_ == TEXT_INPUT_TYPE_PASSWORD ||
451         current_type_ == TEXT_INPUT_TYPE_EMAIL)
452       ecore_imf_context_input_panel_return_key_type_set(
453           context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DONE);
454     else {
455       ecore_imf_context_input_panel_return_key_type_set(
456           context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT);
457     }
458   }
459 }
460
461 void IMContextEfl::SetCaretPosition(int position) {
462   if (!context_)
463     return;
464
465   caret_position_ = position;
466   if (!is_keyevent_processing_)
467     ecore_imf_context_cursor_position_set(context_, position);
468 }
469
470 void IMContextEfl::OnCommit(void* event_info) {
471   if (is_ime_ctx_reset_)
472     return;
473
474   composition_ = CompositionText();
475   char* text = static_cast<char*>(event_info);
476   std::u16string text16 = base::UTF8ToUTF16(text);
477   LOG(INFO) << "OnCommit, text: " << text16;
478   // Only add commit to queue, till we dont know if key event
479   // should be handled. It can be default prevented for exactly.
480   commit_queue_.push(text16);
481
482 #if BUILDFLAG(IS_TIZEN_TV)
483   // Fix IMFCommitCallback come but previous preedit_queue_ haven't handle
484   // cause show double text issue.
485   if (!preedit_queue_.empty())
486     preedit_queue_ = PreeditQueue();
487 #endif
488
489   is_surrounding_text_change_in_progress_ = true;
490
491   // sending fake key event if hardware key is not handled as it is
492   // in Webkit.
493   SendFakeCompositionKeyEvent(text16);
494 }
495
496 void IMContextEfl::SendFakeCompositionKeyEvent(const std::u16string& buf) {
497   std::string str = base::UTF16ToUTF8(buf);
498   Evas_Event_Key_Down downEvent;
499   memset(&downEvent, 0, sizeof(Evas_Event_Key_Down));
500   downEvent.key = str.c_str();
501   downEvent.string = str.c_str();
502
503   KeyEvent event = MakeWebKeyEvent(true, &downEvent);
504   event.is_system_key = true;
505
506   // Key code should be '229' except ASCII key event about key down/up event.
507   // It is according to Web spec about key code [1].
508   // On TV WebAPPs case, key code should be '229' including ASCII key event
509   // about key down/up event.
510   // [1] https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/
511   // keyCode-spec.html
512   // [2] http://developer.samsung.com/tv/develop/tutorials/user-input/
513   // text-input-ime-external-keyboard
514   if (event.key_code() == 0)
515     event.set_key_code(static_cast<KeyboardCode>(229));
516
517 #if BUILDFLAG(IS_TIZEN_TV)
518   if (blink::IsTIZENWRT()) {
519     // a-z, A-Z, 0-9
520     int key_code = event.key_code();
521     if ((key_code >= 65 && key_code <= 90) ||
522         (key_code >= 48 && key_code <= 57)) {
523       event.set_key_code(static_cast<KeyboardCode>(229));
524     }
525   }
526 #endif
527
528   is_keyevent_processing_ = true;
529   PushToKeyUpEventQueue(event.key_code());
530 #if BUILDFLAG(IS_TIZEN_TV)
531   // Needed for HBBTV single window, multiwebview scenario.
532   event.SetDispatcher(window_);
533 #endif
534   EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event);
535 }
536
537 void IMContextEfl::SetComposition(const char* buffer) {
538   std::u16string text16 = base::UTF8ToUTF16(buffer);
539   if (!text16.empty())
540     SendFakeCompositionKeyEvent(text16.substr(text16.length() - 1));
541   else
542     SendFakeCompositionKeyEvent(text16);
543
544   composition_ = CompositionText();
545   composition_.text = text16;
546   composition_.ime_text_spans.push_back(
547       ImeTextSpan(ImeTextSpan::Type::kComposition, 0,
548                   composition_.text.length(), ImeTextSpan::Thickness::kThin,
549                   ImeTextSpan::UnderlineStyle::kSolid, SK_ColorTRANSPARENT));
550   composition_.selection = gfx::Range(composition_.text.length());
551
552   is_surrounding_text_change_in_progress_ = true;
553
554   // Only add preedit to queue, till we dont know if key event
555   // should be handled. It can be default prevented for exactly.
556   preedit_queue_.push(composition_);
557 }
558
559 void IMContextEfl::OnPreeditChanged(void* data,
560                                     Ecore_IMF_Context* context,
561                                     void* event_info) {
562   if (is_ime_ctx_reset_)
563     return;
564
565   char* buffer = NULL;
566   ecore_imf_context_preedit_string_get(context, &buffer, 0);
567
568   if (!buffer)
569     return;
570
571   LOG(INFO) << "OnPreeditChanged,text:" << buffer;
572   // 'buffer' is a null teminated utf-8 string,
573   // it should be OK to find '\n' via strchr
574   char* new_line = std::strchr(buffer, '\n');
575
576   // SetComposition does not handle '\n' properly for
577   // 1. Single line input(<input>)
578   // 2. Multiple line input(<textarea>), when not in composition state
579   if (new_line) {
580     if (current_type_ != TEXT_INPUT_TYPE_TEXT_AREA &&
581         current_type_ != TEXT_INPUT_TYPE_CONTENT_EDITABLE) {
582       // 1. Single line case, replace '\n' to ' '
583       do {
584         *new_line = ' ';
585         new_line = std::strchr(new_line + 1, '\n');
586       } while (new_line != NULL);
587     } else if (composition_.text.length() == 0) {
588       // 2. Multiple line case, split to 2 SetComposition:
589       // a. The first line text(without '\n'),
590       // b. All of the text
591       if (new_line == buffer) {
592         // If first line starts with '\n', send a space instead
593         SetComposition(" ");
594       } else {
595         // Send the first line
596         *new_line = '\0';
597         SetComposition(buffer);
598         *new_line = '\n';
599       }
600     }
601   }
602   SetComposition(buffer);
603   free(buffer);
604 }
605
606 // TODO(kbalazs): figure out what do we need from these callbacks.
607 // Tizen-WebKit-efl using all of them.
608
609 void IMContextEfl::OnInputPanelStateChanged(int state) {
610   if (state == ECORE_IMF_INPUT_PANEL_STATE_SHOW) {
611     if (rwh_helper_ && rwh_helper_->EwkView()) {
612       is_showing_ = true;
613       evas_object_smart_callback_call(rwh_helper_->EwkView(),
614                                       "editorclient,ime,opened", 0);
615     }
616   } else if (state == ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
617     if (rwh_helper_ && rwh_helper_->EwkView()) {
618       is_showing_ = false;
619       evas_object_smart_callback_call(rwh_helper_->EwkView(),
620                                       "editorclient,ime,closed", 0);
621     }
622   }
623 }
624
625 void IMContextEfl::OnInputPanelGeometryChanged() {
626   Eina_Rectangle rect;
627   ecore_imf_context_input_panel_geometry_get(context_, &rect.x, &rect.y,
628                                              &rect.w, &rect.h);
629   if (rwh_helper_ && rwh_helper_->EwkView()) {
630     evas_object_smart_callback_call(rwh_helper_->EwkView(),
631                                     "inputmethod,changed",
632                                     static_cast<void*>(&rect));
633   }
634 }
635 void IMContextEfl::OnCandidateInputPanelStateChanged(int state) {
636   if (rwh_helper_ && rwh_helper_->EwkView()) {
637     if (state == ECORE_IMF_CANDIDATE_PANEL_SHOW) {
638       evas_object_smart_callback_call(rwh_helper_->EwkView(),
639                                       "editorclient,candidate,opened", 0);
640     } else {
641       evas_object_smart_callback_call(rwh_helper_->EwkView(),
642                                       "editorclient,candidate,closed", 0);
643     }
644   }
645 }
646
647 void IMContextEfl::OnCandidateInputPanelGeometryChanged() {}
648
649 bool IMContextEfl::OnRetrieveSurrounding(char** text, int* offset) {
650   // |text| is for providing the value of input field and
651   // |offset| is for providing cursor position,
652   // when surrounding text & cursor position is requested from otherside.
653   if (!text && !offset)
654     return false;
655
656   // If surrounding text is requested before the process of text is completed,
657   // return false to ensure the value(surrounding text & cursor position).
658   if (is_surrounding_text_change_in_progress_)
659     return false;
660
661   if (text)
662     *text = strdup(surrounding_text_.c_str());
663
664   if (offset)
665     *offset = caret_position_;
666
667   return true;
668 }
669
670 void IMContextEfl::OnDeleteSurrounding(void* event_info) {
671   if (!HasRenderWidgetHostImpl()) {
672     LOG(ERROR) << "rwhi is nullptr";
673     return;
674   }
675
676   is_surrounding_text_change_in_progress_ = true;
677
678   CancelComposition();
679
680   auto event = static_cast<Ecore_IMF_Event_Delete_Surrounding*>(event_info);
681
682   int before = -(event->offset);
683   int after = event->n_chars + event->offset;
684
685   if (is_transaction_finished_) {
686     if (rwh_helper_) {
687       rwh_helper_->ExtendSelectionAndDelete(before, after);
688     }
689   } else {
690     int begin = event->offset < 0 ? 0 : event->offset;
691     int end = event->n_chars - begin;
692
693     std::vector<ui::ImeTextSpan> ime_text_spans;
694     ui::ImeTextSpan text_span;
695 #if BUILDFLAG(IS_TIZEN_TV)
696     text_span.underline_style = ui::ImeTextSpan::UnderlineStyle::kNone;
697 #endif
698     ime_text_spans.push_back(text_span);
699     if (rwh_helper_) {
700       rwh_helper_->SetCompositionFromExistingText(begin, end, ime_text_spans);
701     }
702   }
703 }
704
705 void IMContextEfl::OnCandidateInputPanelLanguageChanged(Ecore_IMF_Context*) {
706   if (!HasRenderWidgetHostImpl() || composition_.text.length() == 0)
707     return;
708
709   CancelComposition();
710 }
711
712 bool IMContextEfl::IsVisible() const {
713   if (!context_)
714     return false;
715
716   // API 'ecore_imf_context_input_panel_state_get()' seems
717   // to be buggy(ISF guys said it's an ASYNC api).
718   // Even if virtual keyboard is shown,
719   // the API 'ecore_imf_context_input_panel_state_get()' might
720   // returns '1' which means the keyboard is not shown.
721   return is_showing_;
722 }
723
724 bool IMContextEfl::IsFocused() const {
725   if (!context_)
726     return false;
727
728   return is_focused_;
729 }
730
731 bool IMContextEfl::WebViewWillBeResized() {
732   if (!context_ || !rwh_helper_ || !rwh_helper_->IsOffscreenMode())
733     return false;
734
735   return IsVisible()
736              ? default_view_size_ == rwh_helper_->GetPhysicalBackingSize()
737              : default_view_size_ != rwh_helper_->GetPhysicalBackingSize();
738 }
739
740 void IMContextEfl::ClearQueues() {
741   commit_queue_ = CommitQueue();
742   preedit_queue_ = PreeditQueue();
743   // ClearQueues only called to reset composition state.
744   // To make sure keydown and keyup events are paired,
745   // keydown & keyup event queues should not be cleared.
746   // keyup_event_queue_ = KeyUpEventQueue();
747   // keydown_event_queue_ = KeyDownEventQueue();
748 }
749
750 void IMContextEfl::IMFCommitCallback(void* data,
751                                      Ecore_IMF_Context*,
752                                      void* event_info) {
753   static_cast<IMContextEfl*>(data)->OnCommit(event_info);
754 }
755
756 void IMContextEfl::IMFPreeditChangedCallback(void* data,
757                                              Ecore_IMF_Context* context,
758                                              void* event_info) {
759   static_cast<IMContextEfl*>(data)->OnPreeditChanged(data, context, event_info);
760 }
761
762 void IMContextEfl::IMFInputPanelStateChangedCallback(void* data,
763                                                      Ecore_IMF_Context*,
764                                                      int state) {
765   static_cast<IMContextEfl*>(data)->OnInputPanelStateChanged(state);
766 }
767
768 void IMContextEfl::IMFInputPanelGeometryChangedCallback(void* data,
769                                                         Ecore_IMF_Context*,
770                                                         int state) {
771   static_cast<IMContextEfl*>(data)->OnInputPanelGeometryChanged();
772 }
773
774 void IMContextEfl::IMFCandidatePanelStateChangedCallback(void* data,
775                                                          Ecore_IMF_Context*,
776                                                          int state) {
777   static_cast<IMContextEfl*>(data)->OnCandidateInputPanelStateChanged(state);
778 }
779
780 void IMContextEfl::IMFCandidatePanelGeometryChangedCallback(void* data,
781                                                             Ecore_IMF_Context*,
782                                                             int state) {
783   static_cast<IMContextEfl*>(data)->OnCandidateInputPanelGeometryChanged();
784 }
785
786 Eina_Bool IMContextEfl::IMFRetrieveSurroundingCallback(void* data,
787                                                        Ecore_IMF_Context*,
788                                                        char** text,
789                                                        int* offset) {
790   return static_cast<IMContextEfl*>(data)->OnRetrieveSurrounding(text, offset);
791 }
792
793 void IMContextEfl::IMFDeleteSurroundingCallback(void* data,
794                                                 Ecore_IMF_Context*,
795                                                 void* event_info) {
796   static_cast<IMContextEfl*>(data)->OnDeleteSurrounding(event_info);
797 }
798
799 void IMContextEfl::IMFCandidatePanelLanguageChangedCallback(
800     void* data,
801     Ecore_IMF_Context* context,
802     int value) {
803   static_cast<IMContextEfl*>(data)->OnCandidateInputPanelLanguageChanged(
804       context);
805 }
806
807 // static
808 void IMContextEfl::IMFTransactionPrivateCommandSendCallback(
809     void* data,
810     Ecore_IMF_Context* context,
811     void* eventInfo) {
812   if (!data || !eventInfo)
813     return;
814
815   char* cmd = static_cast<char*>(eventInfo);
816   auto imce = static_cast<IMContextEfl*>(data);
817   if (!strcmp(cmd, "TRANSACTION_START")) {
818     imce->is_transaction_finished_ = false;
819     imce->composition_ = CompositionText();
820   } else if (!strcmp(cmd, "TRANSACTION_END")) {
821     imce->is_transaction_finished_ = true;
822 #if BUILDFLAG(IS_TIZEN_TV)
823     // When using mobile IME input, build the same process as TV IME:
824     // add SetComposition empty text after
825     // SetCompositionFromExistingText/OnCommit, to fix can not
826     // ScrollRectToVisible to RevealSelection when input the text exceed the
827     // input box length.
828     imce->SetComposition("");
829 #endif
830   }
831 }
832
833 void IMContextEfl::HandleKeyEvent(bool is_key_down, bool processed) {
834   if (is_key_down) {
835     LOG(INFO) << "HandleKeyEvent,keyDown,processed:" << processed;
836     ProcessNextCommitText(processed);
837     ProcessNextPreeditText(processed);
838     ProcessNextKeyUpEvent();
839     ProcessNextKeyDownEvent();
840   } else {
841     is_keyevent_processing_ = false;
842     ecore_imf_context_cursor_position_set(context_, caret_position_);
843   }
844 }
845
846 void IMContextEfl::ProcessNextCommitText(bool processed) {
847   if (commit_queue_.empty())
848     return;
849
850   while (!commit_queue_.empty()) {
851     if (!processed && !(commit_queue_.front().empty())) {
852       LOG(INFO) << "ProcessNextCommitText,text:" << commit_queue_.front();
853       ConfirmComposition(commit_queue_.front());
854     }
855     commit_queue_.pop();
856   }
857 }
858
859 void IMContextEfl::ProcessNextPreeditText(bool processed) {
860   if (preedit_queue_.empty())
861     return;
862
863   if (!processed) {
864     LOG(INFO) << "ProcessNextPreeditText,text:" << preedit_queue_.front().text;
865     auto& composition_text = preedit_queue_.front();
866     // ECORE_IMF_CALLBACK_PREEDIT_CHANGED is came about H/W BackKey
867     // in KOREAN ISF engine.
868     // It removes selected text because '' is came as composition text.
869     // Do not request ImeSetComposition to prevent delivering empty string
870     // if text is selected status.
871     bool text_selected = composition_text.text.length() == 0;
872     if (rwh_helper_) {
873       text_selected =
874           text_selected && rwh_helper_ && rwh_helper_->ExistsSelectedText();
875     }
876     if (!text_selected) {
877       if (HasRenderWidgetHostImpl()) {
878         const std::vector<ImeTextSpan>& underlines =
879             reinterpret_cast<const std::vector<ImeTextSpan>&>(
880                 composition_text.ime_text_spans);
881         rwh_helper_->ImeSetComposition(composition_text.text, underlines,
882                                        gfx::Range::InvalidRange(),
883                                        composition_text.selection.start(),
884                                        composition_text.selection.end());
885       } else
886         LOG(ERROR) << "rwhi is nullptr";
887     }
888   } else {
889     ResetIMFContext();
890   }
891
892   preedit_queue_.pop();
893 }
894
895 void IMContextEfl::ProcessNextKeyDownEvent() {
896   if (keydown_event_queue_.empty())
897     return;
898
899   LOG(INFO) << "ProcessNextKeyDownEvent,key:"
900             << keydown_event_queue_.front().key_code();
901 #if BUILDFLAG(IS_TIZEN_TV)
902   // Needed for HBBTV single window, multiwebview scenario.
903   keydown_event_queue_.front().SetDispatcher(window_);
904 #endif
905   EflPlatformEventSource::GetInstance()->DispatchEflEvent(
906       &keydown_event_queue_.front());
907   keydown_event_queue_.pop();
908 }
909
910 void IMContextEfl::ProcessNextKeyUpEvent() {
911   if (keyup_event_queue_.empty())
912     return;
913
914   LOG(INFO) << "ProcessNextKeyUpEvent,key:" << keyup_event_queue_.front();
915   KeyEvent event(ET_KEY_RELEASED, keyup_event_queue_.front(), EF_NONE,
916                  base::TimeTicks());
917 #if BUILDFLAG(IS_TIZEN_TV)
918   // Needed for HBBTV single window, multiwebview scenario.
919   event.SetDispatcher(window_);
920 #endif
921   EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event);
922   keyup_event_queue_.pop();
923 }
924
925 void IMContextEfl::PushToKeyUpEventQueue(KeyboardCode code) {
926   keyup_event_queue_.push(code);
927 }
928
929 void IMContextEfl::PushToKeyDownEventQueue(KeyEvent key) {
930   keydown_event_queue_.push(key);
931 }
932
933 bool IMContextEfl::HasRenderWidgetHostImpl() const {
934   return rwh_helper_ && rwh_helper_->HasRenderWidgetHost();
935 }
936
937 void IMContextEfl::ConfirmComposition(std::u16string& text) {
938   if (!HasRenderWidgetHostImpl()) {
939     LOG(ERROR) << "rwhi is nullptr";
940     return;
941   }
942
943   if (text.length()) {
944     rwh_helper_->ImeCommitText(text, std::vector<ImeTextSpan>(),
945                                gfx::Range::InvalidRange(), 0);
946   }
947   rwh_helper_->ImeFinishComposingText(false);
948 }
949
950 void IMContextEfl::SetSurroundingText(std::string value) {
951   is_surrounding_text_change_in_progress_ = false;
952   surrounding_text_ = value;
953
954 #if BUILDFLAG(IS_TIZEN_TV)
955   // For password input type,disable "Done" key when inputed text not meet
956   // minlength
957   surrounding_text_length_ = base::UTF8ToUTF16(value).length();
958
959   if (ui::TEXT_INPUT_TYPE_PASSWORD == current_type_ &&
960       password_input_minlength_ != -1) {
961     if (surrounding_text_length_ < password_input_minlength_) {
962       ecore_imf_context_input_panel_return_key_disabled_set(context_, true);
963     } else {
964       ecore_imf_context_input_panel_return_key_disabled_set(context_, false);
965     }
966   }
967 #endif
968 }
969
970 bool IMContextEfl::IsShow() {
971   return context_ && is_focused_ && IsIMFVisible(context_);
972 }
973
974 void IMContextEfl::UpdateCaretBounds(const gfx::Rect& caret_bounds) {
975   if (IsIMFVisible(context_)) {
976     int x = caret_bounds.x();
977     int y = caret_bounds.y();
978     int w = caret_bounds.width();
979     int h = caret_bounds.height();
980     ecore_imf_context_cursor_location_set(context_, x, y, w, h);
981   }
982 }
983
984 void IMContextEfl::SetWindowIfNeeded(Evas_Object* evas) {
985   if (ecore_imf_context_client_window_get(context_) &&
986       ecore_imf_context_client_canvas_get(context_)) {
987     return;
988   }
989
990   LOG(INFO) << "Setting IMF window context";
991   Ecore_Window window = ecore_evas_window_get(ecore_evas_ecore_evas_get(evas));
992   ecore_imf_context_client_window_set(context_,
993                                       reinterpret_cast<void*>(window));
994   ecore_imf_context_client_canvas_set(context_, evas);
995 }
996
997 #if BUILDFLAG(IS_TIZEN_TV)
998 void IMContextEfl::SetIMERecommendedWords(const std::string& im_data) {
999   LOG(INFO) << "im_data:" << im_data;
1000   ecore_imf_context_input_panel_imdata_set(context_, im_data.c_str(),
1001                                            im_data.length());
1002 }
1003
1004 void IMContextEfl::SetIMERecommendedWordsType(bool should_enable) {
1005   // currently IME only support enable setting recommended words
1006   // and not support disable it.
1007   // If support later, fix it.
1008   is_get_lookup_table_from_app_ = should_enable;
1009
1010   LOG(INFO) << "should_enable:" << should_enable;
1011   if (should_enable) {
1012     std::string im_data("layouttype=1");
1013     ecore_imf_context_input_panel_imdata_set(context_, im_data.c_str(),
1014                                              im_data.length());
1015   }
1016 }
1017
1018 bool IMContextEfl::ImeHandleKeyEventEnabled() {
1019   return true;
1020 }
1021 #endif
1022
1023 }  // namespace ui