/* * Copyright (C) 2019 Igalia S.L. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config.h" #include "WebKitInputMethodContext.h" #include "WebKitEnumTypes.h" #include "WebKitInputMethodContextPrivate.h" #include "WebKitWebView.h" #include <glib/gi18n-lib.h> #include <wtf/glib/WTFGType.h> using namespace WebCore; /** * SECTION: WebKitInputMethodContext * @Short_description: Base class for input method contexts * @Title: WebKitInputMethodContext * @See_also: #WebKitWebView * * WebKitInputMethodContext defines the interface to implement WebKit input methods. * The input methods are used by WebKit, when editable content is focused, to map from * key events to Unicode character strings. * * An input method may consume multiple key events in sequence and finally * output the composed result. This is called preediting, and an input method * may provide feedback about this process by displaying the intermediate * composition states as preedit text. * * Since: 2.28 */ enum { PROP_0, PROP_INPUT_PURPOSE, PROP_INPUT_HINTS }; enum { PREEDIT_STARTED, PREEDIT_CHANGED, PREEDIT_FINISHED, COMMITTED, DELETE_SURROUNDING, LAST_SIGNAL }; G_DEFINE_BOXED_TYPE(WebKitInputMethodUnderline, webkit_input_method_underline, webkit_input_method_underline_copy, webkit_input_method_underline_free) const CompositionUnderline& webkitInputMethodUnderlineGetCompositionUnderline(WebKitInputMethodUnderline* underline) { return underline->underline; } /** * webkit_input_method_underline_new: * @start_offset: the start offset in preedit string * @end_offset: the end offset in preedit string * * Create a new #WebKitInputMethodUnderline for the given range in preedit string * * Returns: (transfer full): A newly created #WebKitInputMethodUnderline * * Since: 2.28 */ WebKitInputMethodUnderline* webkit_input_method_underline_new(unsigned startOffset, unsigned endOffset) { auto* underline = static_cast<WebKitInputMethodUnderline*>(fastMalloc(sizeof(WebKitInputMethodUnderline))); new (underline) WebKitInputMethodUnderline(startOffset, endOffset); return underline; } /** * webkit_input_method_underline_copy: * @underline: a #WebKitInputMethodUnderline * * Make a copy of the #WebKitInputMethodUnderline. * * Returns: (transfer full): A copy of passed in #WebKitInputMethodUnderline * * Since: 2.28 */ WebKitInputMethodUnderline* webkit_input_method_underline_copy(WebKitInputMethodUnderline* underline) { g_return_val_if_fail(underline, nullptr); auto* copyUnderline = static_cast<WebKitInputMethodUnderline*>(fastMalloc(sizeof(WebKitInputMethodUnderline))); new (copyUnderline) WebKitInputMethodUnderline(underline->underline); return copyUnderline; } /** * webkit_input_method_underline_free: * @underline: A #WebKitInputMethodUnderline * * Free the #WebKitInputMethodUnderline. * * Since: 2.28 */ void webkit_input_method_underline_free(WebKitInputMethodUnderline* underline) { g_return_if_fail(underline); underline->~WebKitInputMethodUnderline(); fastFree(underline); } struct _WebKitInputMethodContextPrivate { WebKitWebView* webView; WebKitInputPurpose purpose; WebKitInputHints hints; }; static guint signals[LAST_SIGNAL] = { 0, }; WEBKIT_DEFINE_ABSTRACT_TYPE(WebKitInputMethodContext, webkit_input_method_context, G_TYPE_OBJECT) /** * WebKitInputMethodContextClass: * @set_enable_preedit: Called via webkit_input_method_context_set_enable_preedit() to * control the use of the preedit string. * @get_preedit: Called via webkit_input_method_context_get_preedit() to * retrieve the text currently being preedited for display at the cursor * position. Any input method which composes complex characters or any * other compositions from multiple sequential key presses should override * this method to provide feedback. * @filter_key_event: Called via webkit_input_method_context_filter_key_event() on every * key press or release event. Every non-trivial input method needs to * override this in order to implement the mapping from key events to text. * A return value of %TRUE indicates to the caller that the event was * consumed by the input method. In that case, the #WebKitInputMethodContext::committed * signal should be emitted upon completion of a key sequence to pass the * resulting text back to the editable element. Alternatively, %FALSE may be * returned to indicate that the event wasn’t handled by the input method. * @notify_focus_in: Called via webkit_input_method_context_notify_focus_in() when * an editable element of the #WebKitWebView has gained focus. * @notify_focus_out: Called via webkit_input_method_context_notify_focus_out() when * an editable element of the #WebKitWebView has lost focus. * @notify_cursor_area: Called via webkit_input_method_context_notify_cursor_area() * to inform the input method of the current cursor location relative to * the client window. * @notify_surrounding: Called via webkit_input_method_context_notify_surrounding() to * update the context surrounding the cursor. The provided text should not include * the preedit string. * @reset: Called via webkit_input_method_context_reset() to signal a change that * requires a reset. An input method that implements preediting * should override this method to clear the preedit state on reset. * * Since: 2.28 */ static void webkitInputMethodContextSetProperty(GObject* object, guint propId, const GValue* value, GParamSpec* paramSpec) { WebKitInputMethodContext* context = WEBKIT_INPUT_METHOD_CONTEXT(object); switch (propId) { case PROP_INPUT_PURPOSE: webkit_input_method_context_set_input_purpose(context, static_cast<WebKitInputPurpose>(g_value_get_enum(value))); break; case PROP_INPUT_HINTS: webkit_input_method_context_set_input_hints(context, static_cast<WebKitInputHints>(g_value_get_flags(value))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec); } } static void webkitInputMethodContextGetProperty(GObject* object, guint propId, GValue* value, GParamSpec* paramSpec) { WebKitInputMethodContext* context = WEBKIT_INPUT_METHOD_CONTEXT(object); switch (propId) { case PROP_INPUT_PURPOSE: g_value_set_enum(value, webkit_input_method_context_get_input_purpose(context)); break; case PROP_INPUT_HINTS: g_value_set_flags(value, webkit_input_method_context_get_input_hints(context)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, paramSpec); } } static void webkit_input_method_context_class_init(WebKitInputMethodContextClass* klass) { GObjectClass* gObjectClass = G_OBJECT_CLASS(klass); gObjectClass->set_property = webkitInputMethodContextSetProperty; gObjectClass->get_property = webkitInputMethodContextGetProperty; /** * WebKitInputMethodContext::input-purpose: * * The #WebKitInputPurpose of the input associated with this context. * * Since: 2.28 */ g_object_class_install_property( gObjectClass, PROP_INPUT_PURPOSE, g_param_spec_enum( "input-purpose", _("Input Purpose"), _("The purpose of the input associated"), WEBKIT_TYPE_INPUT_PURPOSE, WEBKIT_INPUT_PURPOSE_FREE_FORM, WEBKIT_PARAM_READWRITE)); /** * WebKitInputMethodContext::input-hints: * * The #WebKitInputHints of the input associated with this context. * * Since: 2.28 */ g_object_class_install_property( gObjectClass, PROP_INPUT_HINTS, g_param_spec_flags( "input-hints", _("Input Hints"), _("The hints of the input associated"), WEBKIT_TYPE_INPUT_HINTS, WEBKIT_INPUT_HINT_NONE, WEBKIT_PARAM_READWRITE)); /** * WebKitInputMethodContext::preedit-started: * @context: the #WebKitInputMethodContext on which the signal is emitted * * Emitted when a new preediting sequence starts. * * Since: 2.28 */ signals[PREEDIT_STARTED] = g_signal_new( "preedit-started", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(WebKitInputMethodContextClass, preedit_started), nullptr, nullptr, g_cclosure_marshal_generic, G_TYPE_NONE, 0); /** * WebKitInputMethodContext::preedit-changed: * @context: the #WebKitInputMethodContext on which the signal is emitted * * Emitted whenever the preedit sequence currently being entered has changed. * It is also emitted at the end of a preedit sequence, in which case * webkit_input_method_context_get_preedit() returns the empty string. * * Since: 2.28 */ signals[PREEDIT_CHANGED] = g_signal_new( "preedit-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(WebKitInputMethodContextClass, preedit_changed), nullptr, nullptr, g_cclosure_marshal_generic, G_TYPE_NONE, 0); /** * WebKitInputMethodContext::preedit-finished: * @context: the #WebKitInputMethodContext on which the signal is emitted * * Emitted when a preediting sequence has been completed or canceled. * * Since: 2.28 */ signals[PREEDIT_FINISHED] = g_signal_new( "preedit-finished", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(WebKitInputMethodContextClass, preedit_finished), nullptr, nullptr, g_cclosure_marshal_generic, G_TYPE_NONE, 0); /** * WebKitInputMethodContext::committed: * @context: the #WebKitInputMethodContext on which the signal is emitted * @text: the string result * * Emitted when a complete input sequence has been entered by the user. * This can be a single character immediately after a key press or the * final result of preediting. * * Since: 2.28 */ signals[COMMITTED] = g_signal_new( "committed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(WebKitInputMethodContextClass, committed), nullptr, nullptr, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_STRING); /** * WebKitInputMethodContext::delete-surrounding: * @context: the #WebKitInputMethodContext on which the signal is emitted * @offset: the character offset from the cursor position of the text to be deleted. * @n_chars: the number of characters to be deleted * * Emitted when the input method wants to delete the context surrounding the cursor. * If @offset is a negative value, it means a position before the cursor. * * Since: 2.28 */ signals[DELETE_SURROUNDING] = g_signal_new( "delete-surrounding", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(WebKitInputMethodContextClass, delete_surrounding), nullptr, nullptr, g_cclosure_marshal_generic, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_UINT); } void webkitInputMethodContextSetWebView(WebKitInputMethodContext* context, WebKitWebView* webView) { context->priv->webView = webView; } WebKitWebView* webkitInputMethodContextGetWebView(WebKitInputMethodContext* context) { return context->priv->webView; } /** * webkit_input_method_context_set_enable_preedit: * @context: a #WebKitInputMethodContext * @enabled: whether to enable preedit * * Set whether @context should enable preedit to display feedback. * * Since: 2.28 */ void webkit_input_method_context_set_enable_preedit(WebKitInputMethodContext* context, gboolean enabled) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->set_enable_preedit) imClass->set_enable_preedit(context, enabled); } /** * webkit_input_method_context_get_preedit: * @context: a #WebKitInputMethodContext * @text: (out) (transfer full) (nullable): location to store the preedit string * @underlines: (out) (transfer full) (nullable) (element-type WebKit2.InputMethodUnderline): location to store the underlines as a #GList of #WebKitInputMethodUnderline * @cursor_offset: (out) (nullable): location to store the position of cursor in preedit string * * Get the current preedit string for the @context, and a list of WebKitInputMethodUnderline to apply to the string. * The string will be displayed inserted at @cursor_offset. * * Since: 2.28 */ void webkit_input_method_context_get_preedit(WebKitInputMethodContext* context, char** text, GList** underlines, unsigned* cursorOffset) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->get_preedit) { imClass->get_preedit(context, text, underlines, cursorOffset); return; } if (text) *text = g_strdup(""); if (underlines) *underlines = nullptr; if (cursorOffset) *cursorOffset = 0; } /** * webkit_input_method_context_notify_focus_in: * @context: a #WebKitInputMethodContext * * Notify @context that input associated has gained focus. * * Since: 2.28 */ void webkit_input_method_context_notify_focus_in(WebKitInputMethodContext* context) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->notify_focus_in) imClass->notify_focus_in(context); } /** * webkit_input_method_context_notify_focus_out: * @context: a #WebKitInputMethodContext * * Notify @context that input associated has lost focus. * * Since: 2.28 */ void webkit_input_method_context_notify_focus_out(WebKitInputMethodContext* context) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->notify_focus_out) imClass->notify_focus_out(context); } /** * webkit_input_method_context_notify_cursor_area: * @context: a #WebKitInputMethodContext * @x: the x coordinate of cursor location * @y: the y coordinate of cursor location * @width: the width of cursor area * @height: the height of cursor area * * Notify @context that cursor area changed in input associated. * * Since: 2.28 */ void webkit_input_method_context_notify_cursor_area(WebKitInputMethodContext* context, int x, int y, int width, int height) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->notify_cursor_area) imClass->notify_cursor_area(context, x, y, width, height); } /** * webkit_input_method_context_notify_surrounding: * @context: a #WebKitInputMethodContext * @text: text surrounding the insertion point * @length: the length of @text, or -1 if @text is nul-terminated * @cursor_index: the byte index of the insertion cursor within @text. * @selection_index: the byte index of the selection cursor within @text. * * Notify @context that the context surrounding the cursor has changed. * If there's no selection @selection_index is the same as @cursor_index. * * Since: 2.28 */ void webkit_input_method_context_notify_surrounding(WebKitInputMethodContext* context, const char* text, int length, unsigned cursorIndex, unsigned selectionIndex) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); g_return_if_fail(text || !length); if (!text) text = ""; if (length < 0) length = strlen(text); g_return_if_fail(cursorIndex <= static_cast<unsigned>(length)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->notify_surrounding) imClass->notify_surrounding(context, text, length, cursorIndex, selectionIndex); } /** * webkit_input_method_context_reset: * @context: a #WebKitInputMethodContext * * Reset the @context. This will typically cause the input to clear the preedit state. * * Since: 2.28 */ void webkit_input_method_context_reset(WebKitInputMethodContext* context) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); auto* imClass = WEBKIT_INPUT_METHOD_CONTEXT_GET_CLASS(context); if (imClass->reset) imClass->reset(context); } /** * webkit_input_method_context_get_input_purpose: * @context: a #WebKitInputMethodContext * * Get the value of the #WebKitInputMethodContext:input-purpose property. * * Returns: the #WebKitInputPurpose of the input associated with @context * * Since: 2.28 */ WebKitInputPurpose webkit_input_method_context_get_input_purpose(WebKitInputMethodContext* context) { g_return_val_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context), WEBKIT_INPUT_PURPOSE_FREE_FORM); return context->priv->purpose; } /** * webkit_input_method_context_set_input_purpose: * @context: a #WebKitInputMethodContext * @purpose: a #WebKitInputPurpose * * Set the value of the #WebKitInputMethodContext:input-purpose property. * * Since: 2.28 */ void webkit_input_method_context_set_input_purpose(WebKitInputMethodContext* context, WebKitInputPurpose purpose) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); if (context->priv->purpose == purpose) return; context->priv->purpose = purpose; g_object_notify(G_OBJECT(context), "input-purpose"); } /** * webkit_input_method_context_get_input_hints: * @context: a #WebKitInputMethodContext * * Get the value of the #WebKitInputMethodContext:input-hints property. * * Returns: the #WebKitInputHints of the input associated with @context * * Since: 2.28 */ WebKitInputHints webkit_input_method_context_get_input_hints(WebKitInputMethodContext* context) { g_return_val_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context), WEBKIT_INPUT_HINT_NONE); return context->priv->hints; } /* * webkit_input_method_context_set_input_hints: * @context: a #WebKitInputMethodContext * @hints: a #WebKitInputHints * * Set the value of the #WebKitInputMethodContext:input-hints property. * * Since: 2.28 */ void webkit_input_method_context_set_input_hints(WebKitInputMethodContext* context, WebKitInputHints hints) { g_return_if_fail(WEBKIT_IS_INPUT_METHOD_CONTEXT(context)); if (context->priv->hints == hints) return; context->priv->hints = hints; g_object_notify(G_OBJECT(context), "input-hints"); }