2 Copyright (C) 2011 Samsung Electronics
3 Copyright (C) 2012 Intel Corporation. All rights reserved.
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
22 #include "InputMethodContextEfl.h"
24 #include "EwkViewImpl.h"
25 #include "WebPageProxy.h"
26 #include <Ecore_Evas.h>
27 #include <Ecore_IMF_Evas.h>
29 using namespace WebCore;
33 InputMethodContextEfl::InputMethodContextEfl(EwkViewImpl* viewImpl, PassOwnPtr<Ecore_IMF_Context> context)
34 : m_viewImpl(viewImpl)
39 #if ENABLE(TIZEN_ISF_PORT)
40 initializeIMFContext(m_context.get(), ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL);
42 ecore_imf_context_event_callback_add(m_context.get(), ECORE_IMF_CALLBACK_PREEDIT_CHANGED, onIMFPreeditSequenceChanged, this);
43 ecore_imf_context_event_callback_add(m_context.get(), ECORE_IMF_CALLBACK_COMMIT, onIMFInputSequenceComplete, this);
47 InputMethodContextEfl::~InputMethodContextEfl()
51 #if ENABLE(TIZEN_ISF_PORT)
52 void InputMethodContextEfl::onIMFInputPanelStateChanged(void* data, Ecore_IMF_Context*, int state)
54 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
55 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
58 if (state == ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
59 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,ime,closed", 0);
60 if (inputMethodContext->m_context)
61 evas_object_focus_set(inputMethodContext->m_viewImpl->view(), false);
62 } else if (state == ECORE_IMF_INPUT_PANEL_STATE_SHOW)
63 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,ime,opened", 0);
66 void InputMethodContextEfl::onIMFInputPanelGeometryChanged(void* data, Ecore_IMF_Context*, int value)
68 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
69 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
73 ecore_imf_context_input_panel_geometry_get(inputMethodContext->m_context.get(), &rect.x, &rect.y, &rect.w, &rect.h);
74 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "inputmethod,changed", &rect);
77 void InputMethodContextEfl::onIMFCandidatePanelStateChanged(void* data, Ecore_IMF_Context*, int state)
79 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
80 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
83 if (state == ECORE_IMF_CANDIDATE_PANEL_SHOW)
84 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,opened", 0);
86 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,closed", 0);
89 void InputMethodContextEfl::onIMFCandidatePanelGeometryChanged(void* data, Ecore_IMF_Context*, int)
91 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
92 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
96 ecore_imf_context_candidate_panel_geometry_get(inputMethodContext->m_context.get(), &rect.x, &rect.y, &rect.w, &rect.h);
97 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,changed", &rect);
100 Eina_Bool InputMethodContextEfl::onIMFRetrieveSurrounding(void* data, Ecore_IMF_Context*, char** text, int* offset)
102 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
103 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused || (!text && !offset))
106 String surroundingText;
108 inputMethodContext->m_viewImpl->page()->getSurroundingTextAndCursorOffset(surroundingText, cursorOffset);
111 CString utf8Text(surroundingText.utf8());
112 size_t length = utf8Text.length();
114 *text = static_cast<char*>(malloc((length + 1) * sizeof(char)));
119 strncpy(*text, utf8Text.data(), length);
124 *offset = cursorOffset;
129 void InputMethodContextEfl::onIMFDeleteSurrounding(void* data, Ecore_IMF_Context*, void* eventInfo)
131 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
132 if (!eventInfo || !inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
135 Ecore_IMF_Event_Delete_Surrounding* event = static_cast<Ecore_IMF_Event_Delete_Surrounding*>(eventInfo);
136 inputMethodContext->m_viewImpl->page()->deleteSurroundingText(event->offset, event->n_chars);
139 void InputMethodContextEfl::onIMFInputSequenceComplete(void* data, Ecore_IMF_Context*, void* eventInfo)
141 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
142 if (!eventInfo || !inputMethodContext->m_focused)
145 inputMethodContext->m_viewImpl->page()->confirmComposition(String::fromUTF8(static_cast<char*>(eventInfo)));
148 #if ENABLE(TIZEN_WEBKIT2_SUPPORT_JAPANESE_IME)
149 unsigned getUTF8CharacterIndex(const char* string, unsigned byteIndex)
152 const char* end = string + byteIndex;
154 while (*string && string < end) {
157 if ((*string & 0x80) == 0x00)
159 else if ((*string & 0xe0) == 0xc0)
161 else if ((*string & 0xf0) == 0xe0)
163 else if ((*string & 0xf8) == 0xf0)
165 else if ((*string & 0xfc) == 0xf8)
167 else if ((*string & 0xfe) == 0xfc)
173 while (*string && offset--)
181 void InputMethodContextEfl::onIMFPreeditSequenceChanged(void* data, Ecore_IMF_Context* context, void*)
183 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
185 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
188 WebPageProxy* page = inputMethodContext->m_viewImpl->page();
189 if (!page->focusedFrame())
192 PageClientImpl* pageClient = inputMethodContext->m_viewImpl->pageClient.get();
194 page->getCaretPosition(caretRect);
195 caretRect.scale(pageClient->scaleFactor());
198 evas_object_geometry_get(inputMethodContext->m_viewImpl->view(), &viewX, &viewY, 0, 0);
200 int x = caretRect.x() - pageClient->scrollPosition().x() + viewX;
201 int y = caretRect.y() - pageClient->scrollPosition().y() + viewY;
202 int w = caretRect.width();
203 int h = caretRect.height();
204 ecore_imf_context_cursor_location_set(context, x, y, w, h);
207 Eina_List* preeditAttrs = 0;
208 int cursorPosition = 0;
210 ecore_imf_context_preedit_string_with_attributes_get(context, &buffer, &preeditAttrs, &cursorPosition);
212 String preeditString = String::fromUTF8(buffer);
213 Vector<CompositionUnderline> underlines;
217 #if ENABLE(TIZEN_WEBKIT2_SUPPORT_JAPANESE_IME)
218 Eina_List* listIterator = 0;
219 EINA_LIST_FOREACH(preeditAttrs, listIterator, item) {
220 Ecore_IMF_Preedit_Attr* preeditAttr = static_cast<Ecore_IMF_Preedit_Attr*>(item);
222 unsigned startIndex = getUTF8CharacterIndex(buffer, preeditAttr->start_index);
223 unsigned endIndex = getUTF8CharacterIndex(buffer, preeditAttr->end_index);
224 switch (preeditAttr->preedit_type) {
225 case ECORE_IMF_PREEDIT_TYPE_SUB1:
226 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), false));
228 case ECORE_IMF_PREEDIT_TYPE_SUB2:
229 case ECORE_IMF_PREEDIT_TYPE_SUB3:
230 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(255, 255, 255), false));
232 case ECORE_IMF_PREEDIT_TYPE_SUB4:
233 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(46, 168, 255), false));
235 case ECORE_IMF_PREEDIT_TYPE_SUB5:
236 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(153, 98, 195), false));
238 case ECORE_IMF_PREEDIT_TYPE_SUB6:
239 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(118, 222, 55), false));
241 case ECORE_IMF_PREEDIT_TYPE_SUB7:
242 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(153, 153, 153), false));
249 EINA_LIST_FREE(preeditAttrs, item)
253 if (underlines.isEmpty())
254 underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
256 page->setComposition(preeditString, underlines, cursorPosition);
262 void InputMethodContextEfl::onIMFInputSequenceComplete(void* data, Ecore_IMF_Context*, void* eventInfo)
264 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
265 if (!eventInfo || !inputMethodContext->m_focused)
268 inputMethodContext->m_viewImpl->page()->confirmComposition(String::fromUTF8(static_cast<char*>(eventInfo)));
271 void InputMethodContextEfl::onIMFPreeditSequenceChanged(void* data, Ecore_IMF_Context* context, void*)
273 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
275 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
279 ecore_imf_context_preedit_string_get(context, &buffer, 0);
283 String preeditString = String::fromUTF8(buffer);
285 Vector<CompositionUnderline> underlines;
286 underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
287 inputMethodContext->m_viewImpl->page()->setComposition(preeditString, underlines, 0);
291 PassOwnPtr<Ecore_IMF_Context> InputMethodContextEfl::createIMFContext(Evas* canvas)
293 const char* defaultContextID = ecore_imf_context_default_id_get();
294 if (!defaultContextID)
297 OwnPtr<Ecore_IMF_Context> imfContext = adoptPtr(ecore_imf_context_add(defaultContextID));
301 Ecore_Evas* ecoreEvas = ecore_evas_ecore_evas_get(canvas);
302 ecore_imf_context_client_window_set(imfContext.get(), reinterpret_cast<void*>(ecore_evas_window_get(ecoreEvas)));
303 ecore_imf_context_client_canvas_set(imfContext.get(), canvas);
305 return imfContext.release();
308 void InputMethodContextEfl::handleMouseUpEvent(const Evas_Event_Mouse_Up*)
310 ecore_imf_context_reset(m_context.get());
313 void InputMethodContextEfl::handleKeyDownEvent(const Evas_Event_Key_Down* downEvent, bool* isFiltered)
315 Ecore_IMF_Event inputMethodEvent;
316 ecore_imf_evas_event_key_down_wrap(const_cast<Evas_Event_Key_Down*>(downEvent), &inputMethodEvent.key_down);
318 *isFiltered = ecore_imf_context_filter_event(m_context.get(), ECORE_IMF_EVENT_KEY_DOWN, &inputMethodEvent);
321 void InputMethodContextEfl::updateTextInputState()
323 #if !ENABLE(TIZEN_ISF_PORT)
328 const EditorState& editor = m_viewImpl->page()->editorState();
330 if (editor.isContentEditable) {
331 #if ENABLE(TIZEN_ISF_PORT)
333 ecore_imf_context_cursor_position_set(m_context.get(), editor.cursorPosition);
339 #if ENABLE(TIZEN_ISF_PORT)
340 Ewk_Settings* settings = ewk_view_settings_get(m_viewImpl->view());
341 bool defaultKeypadEnabled = ewk_settings_default_keypad_enabled_get(settings);
343 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
344 if (m_viewImpl->pageClient->isClipboardWindowOpened()) {
345 LOG(ISF, "[FAIL] Clipboard\n");
350 #if ENABLE(TIZEN_INPUT_TAG_EXTENSION)
351 if (editor.inputMethodHints == "date") {
352 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_DATE, editor.surroundingText);
354 } else if (editor.inputMethodHints == "datetime") {
355 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_DATETIME, editor.surroundingText);
357 } else if (editor.inputMethodHints == "datetime-local") {
358 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_DATETIMELOCAL, editor.surroundingText);
360 } else if (editor.inputMethodHints == "month") {
361 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_MONTH, editor.surroundingText);
363 } else if (editor.inputMethodHints == "time") {
364 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_TIME, editor.surroundingText);
366 } else if (editor.inputMethodHints == "week") {
367 ewkViewInputPickerRequest(m_viewImpl->view(), EWK_INPUT_TYPE_WEEK, editor.surroundingText);
371 #if ENABLE(TIZEN_DATALIST_ELEMENT)
372 Vector<String> optionList = m_viewImpl->page()->getFocusedInputElementDataList();
373 if (optionList.size() > 0) {
374 if (editor.inputMethodHints == "tel")
375 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_TELEPHONE, optionList);
376 else if (editor.inputMethodHints == "number")
377 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_NUMBER, optionList);
378 else if (editor.inputMethodHints == "email")
379 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_EMAIL, optionList);
380 else if (editor.inputMethodHints == "url")
381 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_URL, optionList);
383 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_TEXT, optionList);
388 #endif // ENABLE(TIZEN_INPUT_TAG_EXTENSION)
390 bool hasFocus = evas_object_focus_get(m_viewImpl->view());
392 if (!defaultKeypadEnabled) {
394 Eina_Rectangle dummyRectForCustomKeypadCallback;
395 memset(&dummyRectForCustomKeypadCallback, 0, sizeof(Eina_Rectangle));
396 evas_object_smart_callback_call(m_viewImpl->view(), "inputmethod,changed", &dummyRectForCustomKeypadCallback);
401 setType(editor.inputMethodHints);
409 ecore_imf_context_reset(m_context.get());
410 ecore_imf_context_focus_in(m_context.get());
411 #if ENABLE(TIZEN_ISF_PORT)
412 ecore_imf_context_input_panel_show(m_context.get());
414 // input field zoom for external keyboard
415 ewk_view_focused_node_adjust(m_viewImpl->view(), EINA_TRUE);
422 if (editor.hasComposition)
423 m_viewImpl->page()->cancelComposition();
426 ecore_imf_context_reset(m_context.get());
427 ecore_imf_context_focus_out(m_context.get());
428 #if ENABLE(TIZEN_ISF_PORT)
429 ecore_imf_context_input_panel_hide(m_context.get());
435 #if ENABLE(TIZEN_ISF_PORT)
436 void InputMethodContextEfl::initializeIMFContext(Ecore_IMF_Context* context, Ecore_IMF_Input_Panel_Layout layout)
438 ecore_imf_context_input_panel_enabled_set(context, false);
439 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_INPUT_PANEL_STATE_EVENT, onIMFInputPanelStateChanged, this);
440 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, onIMFInputPanelGeometryChanged, this);
441 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT, onIMFCandidatePanelStateChanged, this);
442 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT, onIMFCandidatePanelGeometryChanged, this);
443 ecore_imf_context_retrieve_surrounding_callback_set(context, onIMFRetrieveSurrounding, this);
444 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, onIMFDeleteSurrounding, this);
445 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, onIMFPreeditSequenceChanged, this);
446 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_COMMIT, onIMFInputSequenceComplete, this);
447 ecore_imf_context_input_panel_layout_set(m_context.get(), layout);
450 void InputMethodContextEfl::setType(const String& type)
452 Ecore_IMF_Input_Panel_Layout layout;
453 if (type == "number")
454 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER;
455 else if (type == "email")
456 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL;
457 else if (type == "url")
458 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_URL;
459 else if (type == "tel")
460 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER;
461 else if (type == "password")
462 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD;
464 layout = ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
466 if (m_contextList.contains(layout)) {
468 m_context = m_contextList.take(layout);
469 } else if (!m_context || ecore_imf_context_input_panel_layout_get(m_context.get()) != layout) {
470 OwnPtr<Ecore_IMF_Context> context = createIMFContext(evas_object_evas_get(m_viewImpl->view()));
473 m_context = context.release();
474 initializeIMFContext(m_context.get(), layout);
477 if (type == "password" || type == "plugin")
478 ecore_imf_context_prediction_allow_set(m_context.get(), false);
480 ecore_imf_context_prediction_allow_set(m_context.get(), true);
482 if (type.isEmpty() || type == "textarea")
483 ecore_imf_context_autocapital_type_set(m_context.get(), ECORE_IMF_AUTOCAPITAL_TYPE_SENTENCE);
485 ecore_imf_context_autocapital_type_set(m_context.get(), ECORE_IMF_AUTOCAPITAL_TYPE_NONE);
488 bool InputMethodContextEfl::isShow()
490 return (m_context && m_focused && ecore_imf_context_input_panel_state_get(m_context.get()) != ECORE_IMF_INPUT_PANEL_STATE_HIDE);
493 Ecore_IMF_Autocapital_Type InputMethodContextEfl::autoCapitalType()
495 return (m_context ? ecore_imf_context_autocapital_type_get(m_context.get()) : ECORE_IMF_AUTOCAPITAL_TYPE_NONE);
498 void InputMethodContextEfl::onFocusIn()
500 if (!m_context || !m_focused)
503 ecore_imf_context_focus_in(m_context.get());
504 ecore_imf_context_input_panel_show(m_context.get());
507 void InputMethodContextEfl::onFocusOut()
509 if (!m_context || !m_focused)
512 ecore_imf_context_input_panel_hide(m_context.get());
513 ecore_imf_context_focus_out(m_context.get());
516 void InputMethodContextEfl::revertIMFContext()
521 PassOwnPtr<Ecore_IMF_Context> imfContext = m_context.release();
522 int layout = ecore_imf_context_input_panel_layout_get(imfContext.get());
523 m_contextList.add(layout, imfContext);
526 void InputMethodContextEfl::resetIMFContext()
531 ecore_imf_context_reset(m_context.get());
534 void InputMethodContextEfl::hideIMFContext()
539 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
540 if (m_viewImpl->pageClient->isClipboardWindowOpened())
541 m_viewImpl->pageClient->closeClipboardWindow();
544 if (ecore_imf_context_input_panel_state_get(m_context.get()) != ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
545 ecore_imf_context_reset(m_context.get());
546 ecore_imf_context_input_panel_hide(m_context.get());
547 ecore_imf_context_focus_out(m_context.get());
553 void InputMethodContextEfl::destroyIMFContextList()
555 m_contextList.clear();