gtk2/gtk/gtkimmulticontext.c
Matthias Clasen c297d45b8a gtk: Stop using modifier intents
Reviewing the existing settings, the only backend with
some differences in the modifier intent settings is OS X,
and we would rather have that implemented by interpreting
the existing modifiers in the appropriate way.

                X11      Wayland  Win32    OS X

primary         ctrl     ctrl     ctrl     mod2
mnemonic        alt      alt      alt      alt
context menu    -        -        -        ctrl
extend sel      shift    shift    shift    shift
modify sel      ctrl     ctrl     ctrl     mod2
no text         alt|ctrl alt|ctrl alt|ctrl mod2|ctrl
shift group     varies   -        -        alt

GTK now uses the following modifiers:

primary         ctrl
mnemonic        alt
extend sel      shift
modify sel      ctrl
no text         alt|ctrl

The context menu and shift group intents were not used
in GTK at all.

Update tests to no longer expect <Primary> to roundtrip
through the accelerator parsing and formatting code.
2020-04-06 16:32:03 -04:00

619 lines
18 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 "gtkintl.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtksettings.h"
/**
* SECTION:gtkimmulticontext
* @Short_description: An input method context supporting multiple, loadable input methods
* @Title: GtkIMMulticontext
*/
struct _GtkIMMulticontextPrivate
{
GtkIMContext *slave;
GtkWidget *client_widget;
GdkRectangle cursor_location;
gchar *context_id;
gchar *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_slave (GtkIMMulticontext *multicontext,
GtkIMContext *slave,
gboolean finalizing);
static void gtk_im_multicontext_set_client_widget (GtkIMContext *context,
GtkWidget *widget);
static void gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
gchar **str,
PangoAttrList **attrs,
gint *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 (GtkIMContext *context,
gchar **text,
gint *cursor_index);
static void gtk_im_multicontext_set_surrounding (GtkIMContext *context,
const char *text,
gint len,
gint cursor_index);
static void gtk_im_multicontext_preedit_start_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_preedit_end_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_preedit_changed_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext);
static void gtk_im_multicontext_commit_cb (GtkIMContext *slave,
const gchar *str,
GtkIMMulticontext *multicontext);
static gboolean gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext);
static gboolean gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *slave,
gint offset,
gint n_chars,
GtkIMMulticontext *multicontext);
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 = gtk_im_multicontext_set_surrounding;
im_context_class->get_surrounding = gtk_im_multicontext_get_surrounding;
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->slave = 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_slave (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_slave (GtkIMMulticontext *multicontext,
GtkIMContext *slave,
gboolean finalizing)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
gboolean need_preedit_changed = FALSE;
if (priv->slave)
{
if (!finalizing)
gtk_im_context_reset (priv->slave);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_preedit_start_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_preedit_end_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_preedit_changed_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_commit_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_retrieve_surrounding_cb,
multicontext);
g_signal_handlers_disconnect_by_func (priv->slave,
gtk_im_multicontext_delete_surrounding_cb,
multicontext);
g_object_unref (priv->slave);
priv->slave = NULL;
if (!finalizing)
need_preedit_changed = TRUE;
}
priv->slave = slave;
if (priv->slave)
{
g_object_ref (priv->slave);
propagate_purpose (multicontext);
g_signal_connect (priv->slave, "preedit-start",
G_CALLBACK (gtk_im_multicontext_preedit_start_cb),
multicontext);
g_signal_connect (priv->slave, "preedit-end",
G_CALLBACK (gtk_im_multicontext_preedit_end_cb),
multicontext);
g_signal_connect (priv->slave, "preedit-changed",
G_CALLBACK (gtk_im_multicontext_preedit_changed_cb),
multicontext);
g_signal_connect (priv->slave, "commit",
G_CALLBACK (gtk_im_multicontext_commit_cb),
multicontext);
g_signal_connect (priv->slave, "retrieve-surrounding",
G_CALLBACK (gtk_im_multicontext_retrieve_surrounding_cb),
multicontext);
g_signal_connect (priv->slave, "delete-surrounding",
G_CALLBACK (gtk_im_multicontext_delete_surrounding_cb),
multicontext);
if (!priv->use_preedit) /* Default is TRUE */
gtk_im_context_set_use_preedit (slave, FALSE);
if (priv->client_widget)
gtk_im_context_set_client_widget (slave, priv->client_widget);
if (priv->have_cursor_location)
gtk_im_context_set_cursor_location (slave, &priv->cursor_location);
if (priv->focus_in)
gtk_im_context_focus_in (slave);
}
if (need_preedit_changed)
g_signal_emit_by_name (multicontext, "preedit-changed");
}
static const gchar *
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_slave (GtkIMMulticontext *multicontext)
{
GtkIMMulticontextPrivate *priv = multicontext->priv;
if (!priv->slave)
{
GtkIMContext *slave;
g_free (priv->context_id);
priv->context_id = g_strdup (get_effective_context_id (multicontext));
slave = _gtk_im_module_create (priv->context_id);
if (slave)
{
gtk_im_multicontext_set_slave (multicontext, slave, FALSE);
g_object_unref (slave);
}
}
return priv->slave;
}
static void
im_module_setting_changed (GtkSettings *settings,
GParamSpec *pspec,
GtkIMMulticontext *self)
{
gtk_im_multicontext_set_slave (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 *slave;
GtkSettings *settings;
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);
}
slave = gtk_im_multicontext_get_slave (self);
if (slave)
gtk_im_context_set_client_widget (slave, widget);
}
static void
gtk_im_multicontext_get_preedit_string (GtkIMContext *context,
gchar **str,
PangoAttrList **attrs,
gint *cursor_pos)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
if (slave)
gtk_im_context_get_preedit_string (slave, 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 *slave = gtk_im_multicontext_get_slave (multicontext);
guint keyval, state;
if (slave)
{
return gtk_im_context_filter_keypress (slave, 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))
{
gint len;
gchar 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 *slave = gtk_im_multicontext_get_slave (multicontext);
priv->focus_in = TRUE;
if (slave)
gtk_im_context_focus_in (slave);
}
static void
gtk_im_multicontext_focus_out (GtkIMContext *context)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
priv->focus_in = FALSE;
if (slave)
gtk_im_context_focus_out (slave);
}
static void
gtk_im_multicontext_reset (GtkIMContext *context)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
if (slave)
gtk_im_context_reset (slave);
}
static void
gtk_im_multicontext_set_cursor_location (GtkIMContext *context,
GdkRectangle *area)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMMulticontextPrivate *priv = multicontext->priv;
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
priv->have_cursor_location = TRUE;
priv->cursor_location = *area;
if (slave)
gtk_im_context_set_cursor_location (slave, 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 *slave = gtk_im_multicontext_get_slave (multicontext);
use_preedit = use_preedit != FALSE;
priv->use_preedit = use_preedit;
if (slave)
gtk_im_context_set_use_preedit (slave, use_preedit);
}
static gboolean
gtk_im_multicontext_get_surrounding (GtkIMContext *context,
gchar **text,
gint *cursor_index)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
if (slave)
return gtk_im_context_get_surrounding (slave, text, cursor_index);
else
{
if (text)
*text = NULL;
if (cursor_index)
*cursor_index = 0;
return FALSE;
}
}
static void
gtk_im_multicontext_set_surrounding (GtkIMContext *context,
const char *text,
gint len,
gint cursor_index)
{
GtkIMMulticontext *multicontext = GTK_IM_MULTICONTEXT (context);
GtkIMContext *slave = gtk_im_multicontext_get_slave (multicontext);
if (slave)
gtk_im_context_set_surrounding (slave, text, len, cursor_index);
}
static void
gtk_im_multicontext_preedit_start_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-start");
}
static void
gtk_im_multicontext_preedit_end_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-end");
}
static void
gtk_im_multicontext_preedit_changed_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "preedit-changed");
}
static void
gtk_im_multicontext_commit_cb (GtkIMContext *slave,
const gchar *str,
GtkIMMulticontext *multicontext)
{
g_signal_emit_by_name (multicontext, "commit", str);
}
static gboolean
gtk_im_multicontext_retrieve_surrounding_cb (GtkIMContext *slave,
GtkIMMulticontext *multicontext)
{
gboolean result;
g_signal_emit_by_name (multicontext, "retrieve-surrounding", &result);
return result;
}
static gboolean
gtk_im_multicontext_delete_surrounding_cb (GtkIMContext *slave,
gint offset,
gint 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 slave of the @context.
*
* Returns: the id of the currently active slave
*/
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_slave (context);
return priv->context_id;
}
/**
* gtk_im_multicontext_set_context_id:
* @context: a #GtkIMMulticontext
* @context_id: the id to use
*
* Sets the context id for @context.
*
* This causes the currently active slave of @context to be
* replaced by the slave corresponding to the new context id.
*/
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_slave (context, NULL, FALSE);
}
static void
propagate_purpose (GtkIMMulticontext *context)
{
GtkInputPurpose purpose;
GtkInputHints hints;
if (context->priv->slave == NULL)
return;
g_object_get (context, "input-purpose", &purpose, NULL);
g_object_set (context->priv->slave, "input-purpose", purpose, NULL);
g_object_get (context, "input-hints", &hints, NULL);
g_object_set (context->priv->slave, "input-hints", hints, NULL);
}
static void
gtk_im_multicontext_notify (GObject *object,
GParamSpec *pspec)
{
propagate_purpose (GTK_IM_MULTICONTEXT (object));
}