Files
gtk/gtk/gtkimmulticontext.c
Carlos Garnacho ce6aa73c64 gtkimcontext: Make gtk_im_context_activate_osk() public
Make this API public so that foreign "text" widgets (e.g. VteTerminal)
have a chance to integrate this logic into their own event controllers,
without having to craft the behavior of their own gestures around the
built-in IM gesture.

In order to make it most useful for other backends, a GdkEvent argument
and a boolean return value were added. This might be useful information
for other platforms than Wayland, e.g. all investigation seems to hint
that on Windows only the a11y keyboard is available programmatically
via app launching, so the IM method implementation would need to set
up its own policies for showing up the OSK (e.g. on touch events).
2024-03-11 18:04:14 +01:00

659 lines
21 KiB
C

/* GTK - The GIMP Toolkit
* Copyright (C) 2000 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <locale.h>
#include "gtkimmulticontext.h"
#include "gtkimmoduleprivate.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtksettings.h"
/**
* GtkIMMulticontext:
*
* `GtkIMMulticontext` is an input method context supporting multiple,
* switchable input methods.
*
* Text widgets such as `GtkText` or `GtkTextView` use a `GtkIMMultiContext`
* to implement their `im-module` property for switching between different
* input methods.
*/
struct _GtkIMMulticontextPrivate
{
GtkIMContext *delegate;
GtkWidget *client_widget;
GdkRectangle cursor_location;
char *context_id;
char *context_id_aux;
guint use_preedit : 1;
guint have_cursor_location : 1;
guint focus_in : 1;
};
static void gtk_im_multicontext_notify (GObject *object,
GParamSpec *pspec);
static void gtk_im_multicontext_finalize (GObject *object);
static void gtk_im_multicontext_set_delegate (GtkIMMulticontext *multicontext,
GtkIMContext *delegate,
gboolean finalizing);
static void gtk_im_multicontext_set_client_widget (GtkIMContext *context,
GtkWidget *widget);
static void gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
char **str,
PangoAttrList **attrs,
int *cursor_pos);
static gboolean gtk_im_multicontext_filter_keypress (GtkIMContext *context,
GdkEvent *event);
static void gtk_im_multicontext_focus_in (GtkIMContext *context);
static void gtk_im_multicontext_focus_out (GtkIMContext *context);
static void gtk_im_multicontext_reset (GtkIMContext *context);
static void gtk_im_multicontext_set_cursor_location (GtkIMContext *context,
GdkRectangle *area);
static void gtk_im_multicontext_set_use_preedit (GtkIMContext *context,
gboolean use_preedit);
static gboolean gtk_im_multicontext_get_surrounding_with_selection
(GtkIMContext *context,
char **text,
int *cursor_index,
int *anchor_index);
static void gtk_im_multicontext_set_surrounding_with_selection
(GtkIMContext *context,
const char *text,
int len,
int cursor_index,
int anchor_index);
static void gtk_im_multicontext_preedit_start_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_preedit_end_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_preedit_changed_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_commit_cb (GtkIMContext *delegate,
const char *str,
GtkIMMulticontext *multicontext);
static gboolean gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext);
static gboolean gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *delegate,
int offset,
int n_chars,
GtkIMMulticontext *multicontext);
static gboolean gtk_im_multicontext_activate_osk_with_event (GtkIMContext *context,
GdkEvent *event);
static void propagate_purpose (GtkIMMulticontext *context);
G_DEFINE_TYPE_WITH_PRIVATE (GtkIMMulticontext, gtk_im_multicontext, GTK_TYPE_IM_CONTEXT)
static void
gtk_im_multicontext_class_init (GtkIMMulticontextClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
gobject_class->notify = gtk_im_multicontext_notify;
im_context_class->set_client_widget = gtk_im_multicontext_set_client_widget;
im_context_class->get_preedit_string = gtk_im_multicontext_get_preedit_string;
im_context_class->filter_keypress = gtk_im_multicontext_filter_keypress;
im_context_class->focus_in = gtk_im_multicontext_focus_in;
im_context_class->focus_out = gtk_im_multicontext_focus_out;
im_context_class->reset = gtk_im_multicontext_reset;
im_context_class->set_cursor_location = gtk_im_multicontext_set_cursor_location;
im_context_class->set_use_preedit = gtk_im_multicontext_set_use_preedit;
im_context_class->set_surrounding_with_selection = gtk_im_multicontext_set_surrounding_with_selection;
im_context_class->get_surrounding_with_selection = gtk_im_multicontext_get_surrounding_with_selection;
im_context_class->activate_osk_with_event = gtk_im_multicontext_activate_osk_with_event;
gobject_class->finalize = gtk_im_multicontext_finalize;
}
static void
gtk_im_multicontext_init (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv;
multicontext->priv = gtk_im_multicontext_get_instance_private (multicontext);
priv = multicontext->priv;
priv->delegate = NULL;
priv->use_preedit = TRUE;
priv->have_cursor_location = FALSE;
priv->focus_in = FALSE;
}
/**
* gtk_im_multicontext_new:
*
* Creates a new `GtkIMMulticontext`.
*
* Returns: a new `GtkIMMulticontext`.
*/
GtkIMContext *
gtk_im_multicontext_new (void)
{
return g_object_new (GTK_TYPE_IM_MULTICONTEXT, NULL);
}
static void
gtk_im_multicontext_finalize (GObject *object)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (object);
GtkIMMulticontextPrivate *priv = multicontext->priv;
gtk_im_multicontext_set_delegate (multicontext, NULL, TRUE);
g_free (priv->context_id);
g_free (priv->context_id_aux);
G_OBJECT_CLASS (gtk_im_multicontext_parent_class)->finalize (object);
}
static void
gtk_im_multicontext_set_delegate (GtkIMMulticontext *multicontext,
GtkIMContext *delegate,
gboolean finalizing)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
gboolean need_preedit_changed = FALSE;
if (priv->delegate)
{
if (!finalizing)
gtk_im_context_reset (priv->delegate);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_preedit_start_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_preedit_end_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_preedit_changed_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_commit_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_retrieve_surrounding_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->delegate,
gtk_im_multicontext_delete_surrounding_cb,
multicontext);
if (priv->client_widget)
gtk_im_context_set_client_widget (priv->delegate, NULL);
g_object_unref (priv->delegate);
priv->delegate = NULL;
if (!finalizing)
need_preedit_changed = TRUE;
}
priv->delegate = delegate;
if (priv->delegate)
{
g_object_ref (priv->delegate);
propagate_purpose (multicontext);
g_signal_connect (priv->delegate, "preedit-start",
G_CALLBACK (gtk_im_multicontext_preedit_start_cb),
multicontext);
g_signal_connect (priv->delegate, "preedit-end",
G_CALLBACK (gtk_im_multicontext_preedit_end_cb),
multicontext);
g_signal_connect (priv->delegate, "preedit-changed",
G_CALLBACK (gtk_im_multicontext_preedit_changed_cb),
multicontext);
g_signal_connect (priv->delegate, "commit",
G_CALLBACK (gtk_im_multicontext_commit_cb),
multicontext);
g_signal_connect (priv->delegate, "retrieve-surrounding",
G_CALLBACK (gtk_im_multicontext_retrieve_surrounding_cb),
multicontext);
g_signal_connect (priv->delegate, "delete-surrounding",
G_CALLBACK (gtk_im_multicontext_delete_surrounding_cb),
multicontext);
if (!priv->use_preedit) /* Default is TRUE */
gtk_im_context_set_use_preedit (delegate, FALSE);
if (priv->client_widget)
gtk_im_context_set_client_widget (delegate, priv->client_widget);
if (priv->have_cursor_location)
gtk_im_context_set_cursor_location (delegate, &priv->cursor_location);
if (priv->focus_in)
gtk_im_context_focus_in (delegate);
}
if (need_preedit_changed)
g_signal_emit_by_name (multicontext, "preedit-changed");
}
static const char *
get_effective_context_id (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
GdkDisplay *display;
if (priv->context_id_aux)
return priv->context_id_aux;
if (priv->client_widget)
display = gtk_widget_get_display (priv->client_widget);
else
display = gdk_display_get_default ();
return _gtk_im_module_get_default_context_id (display);
}
static GtkIMContext *
gtk_im_multicontext_get_delegate (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
if (!priv->delegate)
{
GtkIMContext *delegate;
g_free (priv->context_id);
priv->context_id = g_strdup (get_effective_context_id (multicontext));
delegate = _gtk_im_module_create (priv->context_id);
if (delegate)
{
gtk_im_multicontext_set_delegate (multicontext, delegate, FALSE);
g_object_unref (delegate);
}
}
return priv->delegate;
}
static void
im_module_setting_changed (GtkSettings *settings,
GParamSpec *pspec,
GtkIMMulticontext *self)
{
gtk_im_multicontext_set_delegate (self, NULL, FALSE);
}
static void
gtk_im_multicontext_set_client_widget (GtkIMContext *context,
GtkWidget *widget)
{
GtkIMMulticontext *self = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = self->priv;
GtkIMContext *delegate;
GtkSettings *settings;
if (priv->client_widget == widget)
return;
gtk_im_multicontext_set_delegate (self, NULL, TRUE);
if (priv->client_widget != NULL)
{
settings = gtk_widget_get_settings (priv->client_widget);
g_signal_handlers_disconnect_by_func (settings,
im_module_setting_changed,
self);
}
priv->client_widget = widget;
if (widget)
{
settings = gtk_widget_get_settings (widget);
g_signal_connect (settings, "notify::gtk-im-module",
G_CALLBACK (im_module_setting_changed),
self);
delegate = gtk_im_multicontext_get_delegate (self);
if (delegate)
gtk_im_context_set_client_widget (delegate, widget);
}
}
static void
gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
char **str,
PangoAttrList **attrs,
int *cursor_pos)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
if (delegate)
gtk_im_context_get_preedit_string (delegate, str, attrs, cursor_pos);
else
{
if (str)
*str = g_strdup ("");
if (attrs)
*attrs = pango_attr_list_new ();
}
}
static gboolean
gtk_im_multicontext_filter_keypress (GtkIMContext *context,
GdkEvent *event)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
guint keyval, state;
if (delegate)
{
return gtk_im_context_filter_keypress (delegate, event);
}
else
{
GdkModifierType no_text_input_mask;
keyval = gdk_key_event_get_keyval (event);
state = gdk_event_get_modifier_state (event);
no_text_input_mask = GDK_ALT_MASK|GDK_CONTROL_MASK;
if (gdk_event_get_event_type (event) == GDK_KEY_PRESS &&
(state & no_text_input_mask) == 0)
{
gunichar ch;
ch = gdk_keyval_to_unicode (keyval);
if (ch != 0 && !g_unichar_iscntrl (ch))
{
int len;
char buf[10];
len = g_unichar_to_utf8 (ch, buf);
buf[len] = '\0';
g_signal_emit_by_name (multicontext, "commit", buf);
return TRUE;
}
}
}
return FALSE;
}
static void
gtk_im_multicontext_focus_in (GtkIMContext *context)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
priv->focus_in = TRUE;
if (delegate)
gtk_im_context_focus_in (delegate);
}
static void
gtk_im_multicontext_focus_out (GtkIMContext *context)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
priv->focus_in = FALSE;
if (delegate)
gtk_im_context_focus_out (delegate);
}
static void
gtk_im_multicontext_reset (GtkIMContext *context)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
if (delegate)
gtk_im_context_reset (delegate);
}
static void
gtk_im_multicontext_set_cursor_location (GtkIMContext *context,
GdkRectangle *area)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
priv->have_cursor_location = TRUE;
priv->cursor_location = *area;
if (delegate)
gtk_im_context_set_cursor_location (delegate, area);
}
static void
gtk_im_multicontext_set_use_preedit (GtkIMContext *context,
gboolean use_preedit)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
use_preedit = use_preedit != FALSE;
priv->use_preedit = use_preedit;
if (delegate)
gtk_im_context_set_use_preedit (delegate, use_preedit);
}
static gboolean
gtk_im_multicontext_get_surrounding_with_selection (GtkIMContext *context,
char **text,
int *cursor_index,
int *anchor_index)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
if (delegate)
return gtk_im_context_get_surrounding_with_selection (delegate, text, cursor_index, anchor_index);
else
{
if (text)
*text = NULL;
if (cursor_index)
*cursor_index = 0;
if (anchor_index)
*anchor_index = 0;
return FALSE;
}
}
static void
gtk_im_multicontext_set_surrounding_with_selection (GtkIMContext *context,
const char *text,
int len,
int cursor_index,
int anchor_index)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
if (delegate)
gtk_im_context_set_surrounding_with_selection (delegate, text, len, cursor_index, anchor_index);
}
static gboolean
gtk_im_multicontext_activate_osk_with_event (GtkIMContext *context,
GdkEvent *event)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *delegate = gtk_im_multicontext_get_delegate (multicontext);
if (delegate)
return gtk_im_context_activate_osk (delegate, event);
else
return FALSE;
}
static void
gtk_im_multicontext_preedit_start_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-start");
}
static void
gtk_im_multicontext_preedit_end_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-end");
}
static void
gtk_im_multicontext_preedit_changed_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-changed");
}
static void
gtk_im_multicontext_commit_cb (GtkIMContext *delegate,
const char *str,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "commit", str);
}
static gboolean
gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *delegate,
GtkIMMulticontext *multicontext)
{
gboolean result;
g_signal_emit_by_name (multicontext, "retrieve-surrounding", &result);
return result;
}
static gboolean
gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *delegate,
int offset,
int n_chars,
GtkIMMulticontext *multicontext)
{
gboolean result;
g_signal_emit_by_name (multicontext, "delete-surrounding",
offset, n_chars, &result);
return result;
}
/**
* gtk_im_multicontext_get_context_id:
* @context: a `GtkIMMulticontext`
*
* Gets the id of the currently active delegate of the @context.
*
* Returns: the id of the currently active delegate
*/
const char *
gtk_im_multicontext_get_context_id (GtkIMMulticontext *context)
{
GtkIMMulticontextPrivate *priv = context->priv;
g_return_val_if_fail (GTK_IS_IM_MULTICONTEXT (context), NULL);
if (priv->context_id == NULL)
gtk_im_multicontext_get_delegate (context);
return priv->context_id;
}
/**
* gtk_im_multicontext_set_context_id:
* @context: a `GtkIMMulticontext`
* @context_id: (nullable): the id to use
*
* Sets the context id for @context.
*
* This causes the currently active delegate of @context to be
* replaced by the delegate corresponding to the new context id.
*
* Setting this to a non-%NULL value overrides the system-wide
* IM module setting. See the [property@Gtk.Settings:gtk-im-module]
* property.
*/
void
gtk_im_multicontext_set_context_id (GtkIMMulticontext *context,
const char *context_id)
{
GtkIMMulticontextPrivate *priv;
g_return_if_fail (GTK_IS_IM_MULTICONTEXT (context));
priv = context->priv;
gtk_im_context_reset (GTK_IM_CONTEXT (context));
g_free (priv->context_id_aux);
priv->context_id_aux = g_strdup (context_id);
gtk_im_multicontext_set_delegate (context, NULL, FALSE);
}
static void
propagate_purpose (GtkIMMulticontext *context)
{
GtkInputPurpose purpose;
GtkInputHints hints;
if (context->priv->delegate == NULL)
return;
g_object_get (context, "input-purpose", &purpose, NULL);
g_object_set (context->priv->delegate, "input-purpose", purpose, NULL);
g_object_get (context, "input-hints", &hints, NULL);
g_object_set (context->priv->delegate, "input-hints", hints, NULL);
}
static void
gtk_im_multicontext_notify (GObject *object,
GParamSpec *pspec)
{
propagate_purpose (GTK_IM_MULTICONTEXT (object));
}