blob: 93d7757eb1912de9d69fc5d96e9e9db13820e229 [file] [log] [blame]
/*
* 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.
*
* Notify @context that the context surrounding the cursor has changed.
*
* Since: 2.28
*/
void webkit_input_method_context_notify_surrounding(WebKitInputMethodContext* context, const char* text, int length, unsigned cursorIndex)
{
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);
}
/**
* 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");
}