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.
5 #include "ui/ozone/platform/efl/im_context_efl.h"
7 #include <Ecore_Evas.h>
8 #include <Ecore_IMF_Evas.h>
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"
20 #if BUILDFLAG(IS_TIZEN_TV)
21 #include "third_party/blink/public/platform/web_application_type.h"
25 #define IM_CTX_LOG_CHANNEL LOG(ERROR)
27 #define IM_CTX_LOG_CHANNEL LOG(INFO)
30 #define IM_CTX_LOG IM_CTX_LOG_CHANNEL << "## IMCTX ## " << __FUNCTION__ << " "
34 Ecore_IMF_Context* CreateIMFContext(Evas* evas) {
36 const char* default_context_id = ecore_imf_context_default_id_get();
37 if (!default_context_id) {
38 LOG(ERROR) << "no default context id";
41 Ecore_IMF_Context* context = ecore_imf_context_add(default_context_id);
43 LOG(ERROR) << "cannot create context";
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);
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;
64 std::unique_ptr<IMContextEfl> IMContextEfl::Create(EflWindow* window) {
65 Ecore_IMF_Context* context = CreateIMFContext(window->evas());
69 return std::make_unique<IMContextEfl>(context, window);
72 IMContextEfl::IMContextEfl(Ecore_IMF_Context* context, EflWindow* window)
73 : context_(context), window_(window) {
75 InitializeIMFContext();
78 void IMContextEfl::InitializeIMFContext() {
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));
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);
117 IMContextEfl::~IMContextEfl() {
118 ecore_imf_context_event_callback_del(context_, ECORE_IMF_CALLBACK_COMMIT,
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_);
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),
151 *was_filtered = ecore_imf_context_filter_event(
152 context_, ECORE_IMF_EVENT_KEY_DOWN, &im_event);
153 is_keyevent_processing_ = true;
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),
161 *was_filtered = ecore_imf_context_filter_event(
162 context_, ECORE_IMF_EVENT_KEY_UP, &im_event);
165 void IMContextEfl::UpdateInputMethodType(TextInputType type,
167 bool can_compose_inline
168 #if BUILDFLAG(IS_TIZEN_TV)
170 int password_input_minlength,
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
184 #if BUILDFLAG(IS_TIZEN_TV)
185 password_input_minlength_ = password_input_minlength;
186 input_maxlength_ = input_maxlength;
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;
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;
202 case TEXT_INPUT_TYPE_TEXT:
203 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
205 case TEXT_INPUT_TYPE_PASSWORD:
206 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD;
207 allow_prediction = false;
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;
213 case TEXT_INPUT_TYPE_EMAIL:
214 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL;
216 case TEXT_INPUT_TYPE_NUMBER:
217 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER;
219 case TEXT_INPUT_TYPE_TELEPHONE:
220 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER;
222 case TEXT_INPUT_TYPE_URL:
223 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL;
225 case TEXT_INPUT_TYPE_MONTH:
226 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_MONTH;
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;
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;
246 case TEXT_INPUT_TYPE_NONE:
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;
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");
263 if (allow_prediction && is_get_lookup_table_from_app_)
264 im_data.append("&layouttype=1");
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_));
270 ecore_imf_context_input_panel_imdata_set(context_, im_data.c_str(),
273 // For password input type,disable "Done" key when inputed text not meet
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;
284 Ecore_IMF_Input_Mode imf_mode;
286 case TEXT_INPUT_MODE_NUMERIC:
287 imf_mode = ECORE_IMF_INPUT_MODE_NUMERIC;
290 imf_mode = ECORE_IMF_INPUT_MODE_ALPHA;
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_,
301 #if BUILDFLAG(IS_TIZEN)
302 Ecore_IMF_Input_Hints hints;
304 hints = static_cast<Ecore_IMF_Input_Hints>(
305 ecore_imf_context_input_hint_get(context_) |
306 ECORE_IMF_INPUT_HINT_MULTILINE);
308 hints = static_cast<Ecore_IMF_Input_Hints>(
309 ecore_imf_context_input_hint_get(context_) &
310 ~ECORE_IMF_INPUT_HINT_MULTILINE);
312 ecore_imf_context_input_hint_set(context_, hints);
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);
320 current_mode_ = mode;
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
328 // So some keys are pushed to KeyDownEventQueue first
329 // then it will be porcessed after composition text is processed.
331 (IsPreeditQueueEmpty() || IsKeyUpQueueEmpty()) &&
332 !(!IsCommitQueueEmpty() &&
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"))));
340 void IMContextEfl::UpdateInputMethodState(TextInputType type,
341 bool can_compose_inline,
343 #if BUILDFLAG(IS_TIZEN_TV)
345 int password_input_minlength,
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);
356 if (current_type_ != type || can_compose_inline_ != can_compose_inline) {
357 UpdateInputMethodType(type, TEXT_INPUT_MODE_DEFAULT, can_compose_inline);
361 current_type_ = type;
362 can_compose_inline_ = can_compose_inline;
364 bool focus_in = type != TEXT_INPUT_TYPE_NONE;
365 if (focus_in == is_focused_ && (!show_if_needed || IsVisible()))
368 if (focus_in && show_if_needed) {
372 else if (IsVisible()) {
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_);
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_);
393 void IMContextEfl::OnFocusIn() {
394 if (current_type_ == ui::TEXT_INPUT_TYPE_NONE)
397 LOG(INFO) << "IME Focus In";
398 ecore_imf_context_focus_in(context_);
402 void IMContextEfl::OnFocusOut() {
403 LOG(INFO) << "IME Focus Out";
407 ecore_imf_context_focus_out(context_);
410 void IMContextEfl::ResetIMFContext() {
411 is_ime_ctx_reset_ = true;
412 ecore_imf_context_reset(context_);
413 is_ime_ctx_reset_ = false;
416 void IMContextEfl::CancelComposition() {
420 if (composition_.text.length() > 0) {
421 std::u16string empty;
422 ConfirmComposition(empty);
423 composition_.text = u"";
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.
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) {
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);
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);
455 ecore_imf_context_input_panel_return_key_type_set(
456 context_, ECORE_IMF_INPUT_PANEL_RETURN_KEY_TYPE_DEFAULT);
461 void IMContextEfl::SetCaretPosition(int position) {
465 caret_position_ = position;
466 if (!is_keyevent_processing_)
467 ecore_imf_context_cursor_position_set(context_, position);
470 void IMContextEfl::OnCommit(void* event_info) {
471 if (is_ime_ctx_reset_)
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);
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();
489 is_surrounding_text_change_in_progress_ = true;
491 // sending fake key event if hardware key is not handled as it is
493 SendFakeCompositionKeyEvent(text16);
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();
503 KeyEvent event = MakeWebKeyEvent(true, &downEvent);
504 event.is_system_key = true;
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/
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));
517 #if BUILDFLAG(IS_TIZEN_TV)
518 if (blink::IsTIZENWRT()) {
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));
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_);
534 EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event);
537 void IMContextEfl::SetComposition(const char* buffer) {
538 std::u16string text16 = base::UTF8ToUTF16(buffer);
540 SendFakeCompositionKeyEvent(text16.substr(text16.length() - 1));
542 SendFakeCompositionKeyEvent(text16);
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());
552 is_surrounding_text_change_in_progress_ = true;
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_);
559 void IMContextEfl::OnPreeditChanged(void* data,
560 Ecore_IMF_Context* context,
562 if (is_ime_ctx_reset_)
566 ecore_imf_context_preedit_string_get(context, &buffer, 0);
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');
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
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 ' '
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
595 // Send the first line
597 SetComposition(buffer);
602 SetComposition(buffer);
606 // TODO(kbalazs): figure out what do we need from these callbacks.
607 // Tizen-WebKit-efl using all of them.
609 void IMContextEfl::OnInputPanelStateChanged(int state) {
610 if (state == ECORE_IMF_INPUT_PANEL_STATE_SHOW) {
611 if (rwh_helper_ && rwh_helper_->EwkView()) {
613 evas_object_smart_callback_call(rwh_helper_->EwkView(),
614 "editorclient,ime,opened", 0);
616 } else if (state == ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
617 if (rwh_helper_ && rwh_helper_->EwkView()) {
619 evas_object_smart_callback_call(rwh_helper_->EwkView(),
620 "editorclient,ime,closed", 0);
625 void IMContextEfl::OnInputPanelGeometryChanged() {
627 ecore_imf_context_input_panel_geometry_get(context_, &rect.x, &rect.y,
629 if (rwh_helper_ && rwh_helper_->EwkView()) {
630 evas_object_smart_callback_call(rwh_helper_->EwkView(),
631 "inputmethod,changed",
632 static_cast<void*>(&rect));
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);
641 evas_object_smart_callback_call(rwh_helper_->EwkView(),
642 "editorclient,candidate,closed", 0);
647 void IMContextEfl::OnCandidateInputPanelGeometryChanged() {}
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)
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_)
662 *text = strdup(surrounding_text_.c_str());
665 *offset = caret_position_;
670 void IMContextEfl::OnDeleteSurrounding(void* event_info) {
671 if (!HasRenderWidgetHostImpl()) {
672 LOG(ERROR) << "rwhi is nullptr";
676 is_surrounding_text_change_in_progress_ = true;
680 auto event = static_cast<Ecore_IMF_Event_Delete_Surrounding*>(event_info);
682 int before = -(event->offset);
683 int after = event->n_chars + event->offset;
685 if (is_transaction_finished_) {
687 rwh_helper_->ExtendSelectionAndDelete(before, after);
690 int begin = event->offset < 0 ? 0 : event->offset;
691 int end = event->n_chars - begin;
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;
698 ime_text_spans.push_back(text_span);
700 rwh_helper_->SetCompositionFromExistingText(begin, end, ime_text_spans);
705 void IMContextEfl::OnCandidateInputPanelLanguageChanged(Ecore_IMF_Context*) {
706 if (!HasRenderWidgetHostImpl() || composition_.text.length() == 0)
712 bool IMContextEfl::IsVisible() const {
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.
724 bool IMContextEfl::IsFocused() const {
731 bool IMContextEfl::WebViewWillBeResized() {
732 if (!context_ || !rwh_helper_ || !rwh_helper_->IsOffscreenMode())
736 ? default_view_size_ == rwh_helper_->GetPhysicalBackingSize()
737 : default_view_size_ != rwh_helper_->GetPhysicalBackingSize();
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();
750 void IMContextEfl::IMFCommitCallback(void* data,
753 static_cast<IMContextEfl*>(data)->OnCommit(event_info);
756 void IMContextEfl::IMFPreeditChangedCallback(void* data,
757 Ecore_IMF_Context* context,
759 static_cast<IMContextEfl*>(data)->OnPreeditChanged(data, context, event_info);
762 void IMContextEfl::IMFInputPanelStateChangedCallback(void* data,
765 static_cast<IMContextEfl*>(data)->OnInputPanelStateChanged(state);
768 void IMContextEfl::IMFInputPanelGeometryChangedCallback(void* data,
771 static_cast<IMContextEfl*>(data)->OnInputPanelGeometryChanged();
774 void IMContextEfl::IMFCandidatePanelStateChangedCallback(void* data,
777 static_cast<IMContextEfl*>(data)->OnCandidateInputPanelStateChanged(state);
780 void IMContextEfl::IMFCandidatePanelGeometryChangedCallback(void* data,
783 static_cast<IMContextEfl*>(data)->OnCandidateInputPanelGeometryChanged();
786 Eina_Bool IMContextEfl::IMFRetrieveSurroundingCallback(void* data,
790 return static_cast<IMContextEfl*>(data)->OnRetrieveSurrounding(text, offset);
793 void IMContextEfl::IMFDeleteSurroundingCallback(void* data,
796 static_cast<IMContextEfl*>(data)->OnDeleteSurrounding(event_info);
799 void IMContextEfl::IMFCandidatePanelLanguageChangedCallback(
801 Ecore_IMF_Context* context,
803 static_cast<IMContextEfl*>(data)->OnCandidateInputPanelLanguageChanged(
808 void IMContextEfl::IMFTransactionPrivateCommandSendCallback(
810 Ecore_IMF_Context* context,
812 if (!data || !eventInfo)
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
828 imce->SetComposition("");
833 void IMContextEfl::HandleKeyEvent(bool is_key_down, bool processed) {
835 LOG(INFO) << "HandleKeyEvent,keyDown,processed:" << processed;
836 ProcessNextCommitText(processed);
837 ProcessNextPreeditText(processed);
838 ProcessNextKeyUpEvent();
839 ProcessNextKeyDownEvent();
841 is_keyevent_processing_ = false;
842 ecore_imf_context_cursor_position_set(context_, caret_position_);
846 void IMContextEfl::ProcessNextCommitText(bool processed) {
847 if (commit_queue_.empty())
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());
859 void IMContextEfl::ProcessNextPreeditText(bool processed) {
860 if (preedit_queue_.empty())
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;
874 text_selected && rwh_helper_ && rwh_helper_->ExistsSelectedText();
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());
886 LOG(ERROR) << "rwhi is nullptr";
892 preedit_queue_.pop();
895 void IMContextEfl::ProcessNextKeyDownEvent() {
896 if (keydown_event_queue_.empty())
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_);
905 EflPlatformEventSource::GetInstance()->DispatchEflEvent(
906 &keydown_event_queue_.front());
907 keydown_event_queue_.pop();
910 void IMContextEfl::ProcessNextKeyUpEvent() {
911 if (keyup_event_queue_.empty())
914 LOG(INFO) << "ProcessNextKeyUpEvent,key:" << keyup_event_queue_.front();
915 KeyEvent event(ET_KEY_RELEASED, keyup_event_queue_.front(), EF_NONE,
917 #if BUILDFLAG(IS_TIZEN_TV)
918 // Needed for HBBTV single window, multiwebview scenario.
919 event.SetDispatcher(window_);
921 EflPlatformEventSource::GetInstance()->DispatchEflEvent(&event);
922 keyup_event_queue_.pop();
925 void IMContextEfl::PushToKeyUpEventQueue(KeyboardCode code) {
926 keyup_event_queue_.push(code);
929 void IMContextEfl::PushToKeyDownEventQueue(KeyEvent key) {
930 keydown_event_queue_.push(key);
933 bool IMContextEfl::HasRenderWidgetHostImpl() const {
934 return rwh_helper_ && rwh_helper_->HasRenderWidgetHost();
937 void IMContextEfl::ConfirmComposition(std::u16string& text) {
938 if (!HasRenderWidgetHostImpl()) {
939 LOG(ERROR) << "rwhi is nullptr";
944 rwh_helper_->ImeCommitText(text, std::vector<ImeTextSpan>(),
945 gfx::Range::InvalidRange(), 0);
947 rwh_helper_->ImeFinishComposingText(false);
950 void IMContextEfl::SetSurroundingText(std::string value) {
951 is_surrounding_text_change_in_progress_ = false;
952 surrounding_text_ = value;
954 #if BUILDFLAG(IS_TIZEN_TV)
955 // For password input type,disable "Done" key when inputed text not meet
957 surrounding_text_length_ = base::UTF8ToUTF16(value).length();
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);
964 ecore_imf_context_input_panel_return_key_disabled_set(context_, false);
970 bool IMContextEfl::IsShow() {
971 return context_ && is_focused_ && IsIMFVisible(context_);
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);
984 void IMContextEfl::SetWindowIfNeeded(Evas_Object* evas) {
985 if (ecore_imf_context_client_window_get(context_) &&
986 ecore_imf_context_client_canvas_get(context_)) {
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);
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(),
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;
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(),
1018 bool IMContextEfl::ImeHandleKeyEventEnabled() {