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)
37 #if ENABLE(TIZEN_ISF_PORT)
38 , m_useInputMethod(false)
39 , m_state(ECORE_IMF_INPUT_PANEL_STATE_HIDE)
40 , m_inputPickerType(-1)
44 #if ENABLE(TIZEN_ISF_PORT)
45 initializeIMFContext(m_context.get(), ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL);
47 ecore_imf_context_event_callback_add(m_context.get(), ECORE_IMF_CALLBACK_PREEDIT_CHANGED, onIMFPreeditSequenceChanged, this);
48 ecore_imf_context_event_callback_add(m_context.get(), ECORE_IMF_CALLBACK_COMMIT, onIMFInputSequenceComplete, this);
52 InputMethodContextEfl::~InputMethodContextEfl()
56 #if ENABLE(TIZEN_ISF_PORT)
57 void InputMethodContextEfl::onIMFInputPanelStateChanged(void* data, Ecore_IMF_Context*, int state)
59 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
61 inputMethodContext->setState(state);
63 if (state == ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
64 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
65 if (inputMethodContext->m_viewImpl->pageClient->isClipboardWindowOpened())
66 inputMethodContext->m_viewImpl->pageClient->closeClipboardWindow();
68 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,ime,closed", 0);
69 } else if (state == ECORE_IMF_INPUT_PANEL_STATE_SHOW)
70 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,ime,opened", 0);
73 void InputMethodContextEfl::onIMFInputPanelGeometryChanged(void* data, Ecore_IMF_Context*, int value)
75 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
78 ecore_imf_context_input_panel_geometry_get(inputMethodContext->m_context.get(), &rect.x, &rect.y, &rect.w, &rect.h);
79 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "inputmethod,changed", &rect);
81 inputMethodContext->setIMERect(IntRect(rect.x, rect.y, rect.w, rect.h));
84 void InputMethodContextEfl::onIMFCandidatePanelStateChanged(void* data, Ecore_IMF_Context*, int state)
86 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
88 if (state == ECORE_IMF_CANDIDATE_PANEL_SHOW)
89 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,opened", 0);
91 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,closed", 0);
94 void InputMethodContextEfl::onIMFCandidatePanelGeometryChanged(void* data, Ecore_IMF_Context*, int)
96 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
99 ecore_imf_context_candidate_panel_geometry_get(inputMethodContext->m_context.get(), &rect.x, &rect.y, &rect.w, &rect.h);
100 evas_object_smart_callback_call(inputMethodContext->m_viewImpl->view(), "editorclient,candidate,changed", &rect);
103 Eina_Bool InputMethodContextEfl::onIMFRetrieveSurrounding(void* data, Ecore_IMF_Context*, char** text, int* offset)
105 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
106 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused || (!text && !offset))
109 String surroundingText;
111 inputMethodContext->m_viewImpl->page()->getSurroundingTextAndCursorOffset(surroundingText, cursorOffset);
114 CString utf8Text(surroundingText.utf8());
115 size_t length = utf8Text.length();
117 *text = static_cast<char*>(malloc((length + 1) * sizeof(char)));
122 strncpy(*text, utf8Text.data(), length);
127 *offset = cursorOffset;
132 void InputMethodContextEfl::onIMFDeleteSurrounding(void* data, Ecore_IMF_Context*, void* eventInfo)
134 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
135 if (!eventInfo || !inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
138 Ecore_IMF_Event_Delete_Surrounding* event = static_cast<Ecore_IMF_Event_Delete_Surrounding*>(eventInfo);
139 inputMethodContext->m_viewImpl->page()->deleteSurroundingText(event->offset, event->n_chars);
142 void InputMethodContextEfl::onIMFInputSequenceComplete(void* data, Ecore_IMF_Context*, void* eventInfo)
144 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
145 if (!eventInfo || !inputMethodContext->m_focused)
148 inputMethodContext->m_viewImpl->page()->confirmComposition(String::fromUTF8(static_cast<char*>(eventInfo)));
151 #if ENABLE(TIZEN_WEBKIT2_SUPPORT_JAPANESE_IME)
152 unsigned getUTF8CharacterIndex(const char* string, unsigned byteIndex)
155 const char* end = string + byteIndex;
157 while (*string && string < end) {
160 if ((*string & 0x80) == 0x00)
162 else if ((*string & 0xe0) == 0xc0)
164 else if ((*string & 0xf0) == 0xe0)
166 else if ((*string & 0xf8) == 0xf0)
168 else if ((*string & 0xfc) == 0xf8)
170 else if ((*string & 0xfe) == 0xfc)
176 while (*string && offset--)
184 void InputMethodContextEfl::onIMFPreeditSequenceChanged(void* data, Ecore_IMF_Context* context, void*)
186 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
188 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
191 WebPageProxy* page = inputMethodContext->m_viewImpl->page();
192 if (!page->focusedFrame())
195 PageClientImpl* pageClient = inputMethodContext->m_viewImpl->pageClient.get();
197 page->getCaretPosition(caretRect);
198 caretRect.scale(pageClient->scaleFactor());
201 evas_object_geometry_get(inputMethodContext->m_viewImpl->view(), &viewX, &viewY, 0, 0);
203 int x = caretRect.x() - pageClient->scrollPosition().x() + viewX;
204 int y = caretRect.y() - pageClient->scrollPosition().y() + viewY;
205 int w = caretRect.width();
206 int h = caretRect.height();
207 ecore_imf_context_cursor_location_set(context, x, y, w, h);
210 Eina_List* preeditAttrs = 0;
211 int cursorPosition = 0;
213 ecore_imf_context_preedit_string_with_attributes_get(context, &buffer, &preeditAttrs, &cursorPosition);
215 String preeditString = String::fromUTF8(buffer);
216 Vector<CompositionUnderline> underlines;
220 #if ENABLE(TIZEN_WEBKIT2_SUPPORT_JAPANESE_IME)
221 Eina_List* listIterator = 0;
222 EINA_LIST_FOREACH(preeditAttrs, listIterator, item) {
223 Ecore_IMF_Preedit_Attr* preeditAttr = static_cast<Ecore_IMF_Preedit_Attr*>(item);
225 unsigned startIndex = getUTF8CharacterIndex(buffer, preeditAttr->start_index);
226 unsigned endIndex = getUTF8CharacterIndex(buffer, preeditAttr->end_index);
227 switch (preeditAttr->preedit_type) {
228 case ECORE_IMF_PREEDIT_TYPE_SUB1:
229 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), false));
231 case ECORE_IMF_PREEDIT_TYPE_SUB2:
232 case ECORE_IMF_PREEDIT_TYPE_SUB3:
233 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(255, 255, 255), false));
235 case ECORE_IMF_PREEDIT_TYPE_SUB4:
236 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(46, 168, 255), false));
238 case ECORE_IMF_PREEDIT_TYPE_SUB5:
239 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(153, 98, 195), false));
241 case ECORE_IMF_PREEDIT_TYPE_SUB6:
242 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(118, 222, 55), false));
244 case ECORE_IMF_PREEDIT_TYPE_SUB7:
245 underlines.append(CompositionUnderline(startIndex, endIndex, Color(0, 0, 0), Color(153, 153, 153), false));
252 EINA_LIST_FREE(preeditAttrs, item)
256 if (underlines.isEmpty())
257 underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
259 page->setComposition(preeditString, underlines, cursorPosition);
265 void InputMethodContextEfl::onIMFInputSequenceComplete(void* data, Ecore_IMF_Context*, void* eventInfo)
267 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
268 if (!eventInfo || !inputMethodContext->m_focused)
271 inputMethodContext->m_viewImpl->page()->confirmComposition(String::fromUTF8(static_cast<char*>(eventInfo)));
274 void InputMethodContextEfl::onIMFPreeditSequenceChanged(void* data, Ecore_IMF_Context* context, void*)
276 InputMethodContextEfl* inputMethodContext = static_cast<InputMethodContextEfl*>(data);
278 if (!inputMethodContext->m_viewImpl->page()->focusedFrame() || !inputMethodContext->m_focused)
282 ecore_imf_context_preedit_string_get(context, &buffer, 0);
286 String preeditString = String::fromUTF8(buffer);
288 Vector<CompositionUnderline> underlines;
289 underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
290 inputMethodContext->m_viewImpl->page()->setComposition(preeditString, underlines, 0);
294 PassOwnPtr<Ecore_IMF_Context> InputMethodContextEfl::createIMFContext(Evas* canvas)
296 const char* defaultContextID = ecore_imf_context_default_id_get();
297 if (!defaultContextID)
300 OwnPtr<Ecore_IMF_Context> imfContext = adoptPtr(ecore_imf_context_add(defaultContextID));
304 Ecore_Evas* ecoreEvas = ecore_evas_ecore_evas_get(canvas);
305 ecore_imf_context_client_window_set(imfContext.get(), reinterpret_cast<void*>(ecore_evas_window_get(ecoreEvas)));
306 ecore_imf_context_client_canvas_set(imfContext.get(), canvas);
308 return imfContext.release();
311 void InputMethodContextEfl::handleMouseUpEvent(const Evas_Event_Mouse_Up*)
313 ecore_imf_context_reset(m_context.get());
316 void InputMethodContextEfl::handleKeyDownEvent(const Evas_Event_Key_Down* downEvent, bool* isFiltered)
318 Ecore_IMF_Event inputMethodEvent;
319 ecore_imf_evas_event_key_down_wrap(const_cast<Evas_Event_Key_Down*>(downEvent), &inputMethodEvent.key_down);
321 *isFiltered = ecore_imf_context_filter_event(m_context.get(), ECORE_IMF_EVENT_KEY_DOWN, &inputMethodEvent);
324 #if ENABLE(TIZEN_ISF_PORT)
325 void InputMethodContextEfl::updateTextInputState()
327 const EditorState& editor = m_viewImpl->page()->editorState();
328 if (editor.shouldIgnoreCompositionSelectionChange || editor.updateEditorRectOnly)
331 if (editor.isContentEditable && m_useInputMethod)
332 showIMFContext(editor);
337 ecore_imf_context_cursor_position_set(m_context.get(), editor.cursorPosition);
340 void InputMethodContextEfl::updateTextInputState()
345 const EditorState& editor = m_viewImpl->page()->editorState();
347 if (editor.isContentEditable) {
351 ecore_imf_context_reset(m_context.get());
352 ecore_imf_context_focus_in(m_context.get());
358 if (editor.hasComposition)
359 m_viewImpl->page()->cancelComposition();
362 ecore_imf_context_reset(m_context.get());
363 ecore_imf_context_focus_out(m_context.get());
368 #if ENABLE(TIZEN_ISF_PORT)
369 void InputMethodContextEfl::initializeIMFContext(Ecore_IMF_Context* context, Ecore_IMF_Input_Panel_Layout layout)
371 ecore_imf_context_input_panel_enabled_set(context, false);
372 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_INPUT_PANEL_STATE_EVENT, onIMFInputPanelStateChanged, this);
373 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_INPUT_PANEL_GEOMETRY_EVENT, onIMFInputPanelGeometryChanged, this);
374 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_CANDIDATE_PANEL_STATE_EVENT, onIMFCandidatePanelStateChanged, this);
375 ecore_imf_context_input_panel_event_callback_add(context, ECORE_IMF_CANDIDATE_PANEL_GEOMETRY_EVENT, onIMFCandidatePanelGeometryChanged, this);
376 ecore_imf_context_retrieve_surrounding_callback_set(context, onIMFRetrieveSurrounding, this);
377 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_DELETE_SURROUNDING, onIMFDeleteSurrounding, this);
378 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_PREEDIT_CHANGED, onIMFPreeditSequenceChanged, this);
379 ecore_imf_context_event_callback_add(context, ECORE_IMF_CALLBACK_COMMIT, onIMFInputSequenceComplete, this);
380 ecore_imf_context_input_panel_layout_set(m_context.get(), layout);
383 void InputMethodContextEfl::setUseInputMethod(bool use)
385 m_useInputMethod = use;
386 updateTextInputState();
389 Ecore_IMF_Input_Panel_Layout InputMethodContextEfl::layoutType(const String& type)
391 if (type == "number")
392 return ECORE_IMF_INPUT_PANEL_LAYOUT_NUMBER;
393 else if (type == "email")
394 return ECORE_IMF_INPUT_PANEL_LAYOUT_EMAIL;
395 else if (type == "url")
396 return ECORE_IMF_INPUT_PANEL_LAYOUT_URL;
397 else if (type == "tel")
398 return ECORE_IMF_INPUT_PANEL_LAYOUT_PHONENUMBER;
399 else if (type == "password")
400 return ECORE_IMF_INPUT_PANEL_LAYOUT_PASSWORD;
402 return ECORE_IMF_INPUT_PANEL_LAYOUT_NORMAL;
405 void InputMethodContextEfl::setIMFContext(Ecore_IMF_Input_Panel_Layout layout, const String& type)
407 if (m_contextList.contains(layout)) {
409 m_context = m_contextList.take(layout);
410 } else if (!m_context || ecore_imf_context_input_panel_layout_get(m_context.get()) != layout) {
411 OwnPtr<Ecore_IMF_Context> context = createIMFContext(evas_object_evas_get(m_viewImpl->view()));
414 m_context = context.release();
415 initializeIMFContext(m_context.get(), layout);
418 if (type == "password" || type == "plugin")
419 ecore_imf_context_prediction_allow_set(m_context.get(), false);
421 ecore_imf_context_prediction_allow_set(m_context.get(), true);
423 if (type.isEmpty() || type == "textarea")
424 ecore_imf_context_autocapital_type_set(m_context.get(), ECORE_IMF_AUTOCAPITAL_TYPE_SENTENCE);
426 ecore_imf_context_autocapital_type_set(m_context.get(), ECORE_IMF_AUTOCAPITAL_TYPE_NONE);
429 bool InputMethodContextEfl::isShow()
431 return (m_context && m_focused && ecore_imf_context_input_panel_state_get(m_context.get()) != ECORE_IMF_INPUT_PANEL_STATE_HIDE);
434 Ecore_IMF_Autocapital_Type InputMethodContextEfl::autoCapitalType()
436 return (m_context ? ecore_imf_context_autocapital_type_get(m_context.get()) : ECORE_IMF_AUTOCAPITAL_TYPE_NONE);
439 void InputMethodContextEfl::onFocusIn()
441 if (m_inputPickerType >= 0) {
442 showInputPicker(m_viewImpl->page()->editorState());
446 if (!m_context || !m_focused)
449 ecore_imf_context_focus_in(m_context.get());
450 ecore_imf_context_input_panel_show(m_context.get());
453 void InputMethodContextEfl::onFocusOut()
455 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
456 if (m_state != ECORE_IMF_INPUT_PANEL_STATE_SHOW) {
457 if (m_viewImpl->pageClient->isClipboardWindowOpened())
458 m_viewImpl->pageClient->closeClipboardWindow();
462 if (!m_context || !m_focused)
465 ecore_imf_context_input_panel_hide(m_context.get());
466 ecore_imf_context_focus_out(m_context.get());
469 void InputMethodContextEfl::revertIMFContext()
474 PassOwnPtr<Ecore_IMF_Context> imfContext = m_context.release();
475 int layout = ecore_imf_context_input_panel_layout_get(imfContext.get());
476 m_contextList.add(layout, imfContext);
479 void InputMethodContextEfl::resetIMFContext()
484 ecore_imf_context_reset(m_context.get());
487 void InputMethodContextEfl::showIMFContext(const EditorState& editor)
489 Ecore_IMF_Input_Panel_Layout layout = layoutType(editor.inputMethodHints);
491 if (m_context && m_state != ECORE_IMF_INPUT_PANEL_STATE_HIDE && layout == ecore_imf_context_input_panel_layout_get(m_context.get()))
494 Ewk_Settings* settings = ewk_view_settings_get(m_viewImpl->view());
495 bool defaultKeypadEnabled = ewk_settings_default_keypad_enabled_get(settings);
497 #if ENABLE(TIZEN_INPUT_TAG_EXTENSION)
498 if (editor.inputMethodHints == "date")
499 m_inputPickerType = EWK_INPUT_TYPE_DATE;
500 else if (editor.inputMethodHints == "datetime")
501 m_inputPickerType = EWK_INPUT_TYPE_DATETIME;
502 else if (editor.inputMethodHints == "datetime-local")
503 m_inputPickerType = EWK_INPUT_TYPE_DATETIMELOCAL;
504 else if (editor.inputMethodHints == "month")
505 m_inputPickerType = EWK_INPUT_TYPE_MONTH;
506 else if (editor.inputMethodHints == "time")
507 m_inputPickerType = EWK_INPUT_TYPE_TIME;
508 else if (editor.inputMethodHints == "week")
509 m_inputPickerType = EWK_INPUT_TYPE_WEEK;
511 m_inputPickerType = -1;
513 if (m_inputPickerType >= 0) {
514 showInputPicker(editor);
518 #if ENABLE(TIZEN_DATALIST_ELEMENT)
519 Vector<String> optionList = m_viewImpl->page()->getFocusedInputElementDataList();
520 if (optionList.size() > 0) {
521 if (editor.selectionIsRange || !evas_object_focus_get(m_viewImpl->view()))
524 if (editor.inputMethodHints == "tel")
525 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_TELEPHONE, optionList);
526 else if (editor.inputMethodHints == "number")
527 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_NUMBER, optionList);
528 else if (editor.inputMethodHints == "email")
529 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_EMAIL, optionList);
530 else if (editor.inputMethodHints == "url")
531 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_URL, optionList);
533 ewkViewDataListShowRequest(m_viewImpl->view(), EWK_INPUT_TYPE_TEXT, optionList);
538 #endif // ENABLE(TIZEN_INPUT_TAG_EXTENSION)
540 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
541 if (m_viewImpl->pageClient->isClipboardWindowOpened()) {
542 LOG(ISF, "[FAIL] Clipboard\n");
547 bool hasFocus = evas_object_focus_get(m_viewImpl->view());
549 if (!defaultKeypadEnabled) {
551 Eina_Rectangle dummyRectForCustomKeypadCallback;
552 memset(&dummyRectForCustomKeypadCallback, 0, sizeof(Eina_Rectangle));
553 evas_object_smart_callback_call(m_viewImpl->view(), "inputmethod,changed", &dummyRectForCustomKeypadCallback);
558 setIMFContext(layout, editor.inputMethodHints);
565 ecore_imf_context_reset(m_context.get());
566 ecore_imf_context_focus_in(m_context.get());
567 ecore_imf_context_input_panel_show(m_context.get());
569 // input field zoom for external keyboard
570 ewk_view_focused_node_adjust(m_viewImpl->view(), EINA_TRUE);
573 m_state = ECORE_IMF_INPUT_PANEL_STATE_WILL_SHOW;
576 void InputMethodContextEfl::hideIMFContext()
578 #if ENABLE(TIZEN_WEBKIT2_CONTEXT_MENU_CLIPBOARD)
579 if (m_state != ECORE_IMF_INPUT_PANEL_STATE_SHOW) {
580 if (m_viewImpl->pageClient->isClipboardWindowOpened())
581 m_viewImpl->pageClient->closeClipboardWindow();
585 m_inputPickerType = -1;
587 if (!m_context || !m_focused)
590 if (m_viewImpl->page()->editorState().hasComposition)
591 m_viewImpl->page()->cancelComposition();
595 if (ecore_imf_context_input_panel_state_get(m_context.get()) != ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
596 ecore_imf_context_reset(m_context.get());
597 ecore_imf_context_input_panel_hide(m_context.get());
598 ecore_imf_context_focus_out(m_context.get());
604 void InputMethodContextEfl::destroyIMFContextList()
606 m_contextList.clear();
609 #if ENABLE(TIZEN_INPUT_TAG_EXTENSION)
610 void InputMethodContextEfl::showInputPicker(const EditorState& editorState)
612 if (editorState.selectionIsRange || !evas_object_focus_get(m_viewImpl->view()))
615 ewkViewInputPickerRequest(m_viewImpl->view(), static_cast<Ewk_Input_Type>(m_inputPickerType), editorState.surroundingText);
616 m_inputPickerType = -1;
620 bool InputMethodContextEfl::isIMEPostion(int x, int y)
622 if (m_state == ECORE_IMF_INPUT_PANEL_STATE_SHOW)
623 return m_imeRect.contains(x, y);