forked from AuroraMiddleware/gtk
Remove the namespace prefix from functions defined locally only. Clean up
* modules/input/gtkimcontextmultipress.[ch]: Remove the namespace prefix from functions defined locally only. Clean up the code and change indentation to match the GTK+ coding style. (_GtkImContextMultipress::key_sequences): Replace array of pointers by GHashTable. Adapt the implementation accordingly. (passthrough_enabled_for_window): Remove. The passthrough hack is no longer necessary thanks to the recently introduced "im-module" property of GtkEntry and GtkTextView. (load_config): Rework to implement an improved configuration file format. Just fetch all keys of the group instead of expecting the keys to be named a certain way. This also allows interpreting the config key itself as the GDK key name to bind the character sequence to, thereby making it independent of the sequence itself. * modules/input/im-multipress.conf: New example configuration using the new syntax. The example sequences are now bound to the numeric keypad and imitate the behavior of a standard mobile phone. svn path=/trunk/; revision=22125
This commit is contained in:
parent
e5e8afb93c
commit
47aae53285
20
ChangeLog
20
ChangeLog
@ -1,3 +1,23 @@
|
|||||||
|
2009-01-16 Daniel Elstner <danielk@openismus.com>
|
||||||
|
|
||||||
|
* modules/input/gtkimcontextmultipress.[ch]: Remove the namespace
|
||||||
|
prefix from functions defined locally only. Clean up the code and
|
||||||
|
change indentation to match the GTK+ coding style.
|
||||||
|
(_GtkImContextMultipress::key_sequences): Replace array of pointers
|
||||||
|
by GHashTable. Adapt the implementation accordingly.
|
||||||
|
(passthrough_enabled_for_window): Remove. The passthrough hack is
|
||||||
|
no longer necessary thanks to the recently introduced "im-module"
|
||||||
|
property of GtkEntry and GtkTextView.
|
||||||
|
(load_config): Rework to implement an improved configuration file
|
||||||
|
format. Just fetch all keys of the group instead of expecting the
|
||||||
|
keys to be named a certain way. This also allows interpreting the
|
||||||
|
config key itself as the GDK key name to bind the character sequence
|
||||||
|
to, thereby making it independent of the sequence itself.
|
||||||
|
|
||||||
|
* modules/input/im-multipress.conf: New example configuration using
|
||||||
|
the new syntax. The example sequences are now bound to the numeric
|
||||||
|
keypad and imitate the behavior of a standard mobile phone.
|
||||||
|
|
||||||
2009-01-15 Murray Cumming <murrayc@murrayc.com>
|
2009-01-15 Murray Cumming <murrayc@murrayc.com>
|
||||||
|
|
||||||
* gtk/gtkimcontext.c: documentation description: Mention the various
|
* gtk/gtkimcontext.c: documentation description: Mention the various
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/* Copyright (C) 2006 Openismus GmbH
|
/*
|
||||||
*
|
* Copyright (c) 2006-2009 Openismus GmbH
|
||||||
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
* License as published by the Free Software Foundation; either
|
* License as published by the Free Software Foundation; either
|
||||||
@ -16,37 +17,43 @@
|
|||||||
* Boston, MA 02111-1307, USA.
|
* Boston, MA 02111-1307, USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <config.h>
|
#include "gtkimcontextmultipress.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include <gdk/gdkkeysyms.h>
|
#include <gdk/gdkkeysyms.h>
|
||||||
#include <gtk/gtkimmodule.h>
|
#include <gtk/gtkimmodule.h>
|
||||||
#include "gtkimcontextmultipress.h"
|
#include <config.h>
|
||||||
|
|
||||||
#define AUTOMATIC_COMPOSE_TIMEOUT 1 /* seconds */
|
#define AUTOMATIC_COMPOSE_TIMEOUT 1 /* seconds */
|
||||||
|
|
||||||
#define CONFIGURATION_FILENAME MULTIPRESS_CONFDIR G_DIR_SEPARATOR_S "im-multipress.conf"
|
#define CONFIGURATION_FILENAME MULTIPRESS_CONFDIR G_DIR_SEPARATOR_S "im-multipress.conf"
|
||||||
|
|
||||||
#define MULTIPRESS_PASSTHROUGH_FLAG "multipress-passthrough-flag"
|
/* This contains rows of characters that can be entered by pressing
|
||||||
|
* a particular key repeatedly. Each row has one key (such as GDK_a),
|
||||||
/** This contains rows of characters that can be inputed by pressing a particular key repeatedly.
|
* and an array of character strings, such as "a".
|
||||||
* Each row has one key (such as GDK_A), and an array of characters, such as 'a'.
|
|
||||||
*/
|
*/
|
||||||
struct _KeySequence
|
typedef struct
|
||||||
{
|
{
|
||||||
gunichar key_press; /* Such as 'a' (== GDK_a) */
|
gchar **characters; /* array of strings */
|
||||||
gchar **characters; /* Array of strings. */
|
gsize n_characters; /* number of strings in the array */
|
||||||
gsize characters_length; /* size of the array of strings. */
|
}
|
||||||
};
|
KeySequence;
|
||||||
|
|
||||||
static void gtk_im_context_multipress_class_init (GtkImContextMultipressClass *klass);
|
static GObjectClass *im_context_multipress_parent_class = NULL;
|
||||||
static void gtk_im_context_multipress_init (GtkImContextMultipress *self);
|
static GType im_context_multipress_type = 0;
|
||||||
static void gtk_im_context_multipress_finalize (GObject *obj);
|
|
||||||
|
|
||||||
static void gtk_im_context_multipress_load_config (GtkImContextMultipress *self);
|
static void im_context_multipress_class_init (GtkImContextMultipressClass *klass);
|
||||||
|
static void im_context_multipress_init (GtkImContextMultipress *self);
|
||||||
|
static void im_context_multipress_finalize (GObject *obj);
|
||||||
|
|
||||||
static GObjectClass *gtk_im_context_multipress_parent_class = NULL;
|
static void load_config (GtkImContextMultipress *self);
|
||||||
static GType gtk_im_multi_press_im_context_type = 0;
|
|
||||||
|
static gboolean vfunc_filter_keypress (GtkIMContext *context,
|
||||||
|
GdkEventKey *event);
|
||||||
|
static void vfunc_reset (GtkIMContext *context);
|
||||||
|
static void vfunc_get_preedit_string (GtkIMContext *context,
|
||||||
|
gchar **str,
|
||||||
|
PangoAttrList **attrs,
|
||||||
|
gint *cursor_pos);
|
||||||
|
|
||||||
/* Notice that we have a *_register_type(GTypeModule*) function instead of a
|
/* Notice that we have a *_register_type(GTypeModule*) function instead of a
|
||||||
* *_get_type() function, because we must use g_type_module_register_type(),
|
* *_get_type() function, because we must use g_type_module_register_type(),
|
||||||
@ -57,20 +64,20 @@ void
|
|||||||
gtk_im_context_multipress_register_type (GTypeModule* type_module)
|
gtk_im_context_multipress_register_type (GTypeModule* type_module)
|
||||||
{
|
{
|
||||||
static const GTypeInfo im_context_multipress_info =
|
static const GTypeInfo im_context_multipress_info =
|
||||||
{
|
{
|
||||||
sizeof (GtkImContextMultipressClass),
|
sizeof (GtkImContextMultipressClass),
|
||||||
(GBaseInitFunc) NULL,
|
(GBaseInitFunc) NULL,
|
||||||
(GBaseFinalizeFunc) NULL,
|
(GBaseFinalizeFunc) NULL,
|
||||||
(GClassInitFunc) gtk_im_context_multipress_class_init,
|
(GClassInitFunc) &im_context_multipress_class_init,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
sizeof (GtkImContextMultipress),
|
sizeof (GtkImContextMultipress),
|
||||||
0,
|
0,
|
||||||
(GInstanceInitFunc) gtk_im_context_multipress_init,
|
(GInstanceInitFunc) &im_context_multipress_init,
|
||||||
0,
|
0,
|
||||||
};
|
};
|
||||||
|
|
||||||
gtk_im_multi_press_im_context_type =
|
im_context_multipress_type =
|
||||||
g_type_module_register_type (type_module,
|
g_type_module_register_type (type_module,
|
||||||
GTK_TYPE_IM_CONTEXT,
|
GTK_TYPE_IM_CONTEXT,
|
||||||
"GtkImContextMultipress",
|
"GtkImContextMultipress",
|
||||||
@ -80,167 +87,75 @@ gtk_im_context_multipress_register_type (GTypeModule* type_module)
|
|||||||
GType
|
GType
|
||||||
gtk_im_context_multipress_get_type (void)
|
gtk_im_context_multipress_get_type (void)
|
||||||
{
|
{
|
||||||
g_assert (gtk_im_multi_press_im_context_type != 0);
|
g_assert (im_context_multipress_type != 0);
|
||||||
return gtk_im_multi_press_im_context_type;
|
|
||||||
|
return im_context_multipress_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Returns TRUE if the passthrough flag is set on the currently focused
|
|
||||||
* child of the widget that owns the GDK window. In order to turn on
|
|
||||||
* passthrough mode, call:
|
|
||||||
* g_object_set_data (widget, "multipress-passthrough-flag", GINT_TO_POINTER (1));
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
passthrough_enabled_for_window (GdkWindow* window)
|
|
||||||
{
|
|
||||||
gpointer event_widget = NULL;
|
|
||||||
|
|
||||||
g_return_val_if_fail (window != NULL, FALSE);
|
|
||||||
/*
|
|
||||||
* For historical reasons, GTK+ assumes the user data attached to a GdkWindow
|
|
||||||
* to point to the GtkWidget that owns the window. It's even documented:
|
|
||||||
* http://developer.gnome.org/doc/API/2.0/gdk/gdk-Windows.html#gdk-window-set-user-data
|
|
||||||
* So we are really lucky here, as this allows us to attach IM state
|
|
||||||
* information to a widget in a fairly straightforward manner.
|
|
||||||
*/
|
|
||||||
gdk_window_get_user_data (window, &event_widget);
|
|
||||||
|
|
||||||
if (event_widget && GTK_IS_WIDGET (event_widget))
|
|
||||||
{
|
|
||||||
GtkWidget *toplevel;
|
|
||||||
GtkWidget *focus_widget;
|
|
||||||
/*
|
|
||||||
* The event window for key presses will usually belong to the toplevel
|
|
||||||
* GtkWindow, but that might not be true for synthetic events. In any
|
|
||||||
* case we need to find the currently focused child widget.
|
|
||||||
*/
|
|
||||||
toplevel = gtk_widget_get_toplevel ((GtkWidget *)event_widget);
|
|
||||||
|
|
||||||
g_return_val_if_fail (toplevel != NULL && GTK_IS_WINDOW (toplevel), FALSE);
|
|
||||||
|
|
||||||
focus_widget = gtk_window_get_focus ((GtkWindow *)toplevel);
|
|
||||||
|
|
||||||
if (focus_widget)
|
|
||||||
{
|
|
||||||
static GQuark quark_passthrough_flag = 0;
|
|
||||||
|
|
||||||
if (!quark_passthrough_flag)
|
|
||||||
quark_passthrough_flag = g_quark_from_string (MULTIPRESS_PASSTHROUGH_FLAG);
|
|
||||||
|
|
||||||
if (g_object_get_qdata (G_OBJECT (focus_widget), quark_passthrough_flag))
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean vfunc_filter_keypress (GtkIMContext *context,
|
|
||||||
GdkEventKey *event);
|
|
||||||
static void vfunc_reset (GtkIMContext *context);
|
|
||||||
static void vfunc_get_preedit_string (GtkIMContext *context,
|
|
||||||
gchar **str,
|
|
||||||
PangoAttrList **attrs,
|
|
||||||
gint *cursor_pos);
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtk_im_context_multipress_class_init (GtkImContextMultipressClass *klass)
|
key_sequence_free (gpointer value)
|
||||||
{
|
{
|
||||||
GtkIMContextClass* im_context_class;
|
KeySequence *seq = value;
|
||||||
|
|
||||||
|
if (seq != NULL)
|
||||||
|
{
|
||||||
|
g_strfreev (seq->characters);
|
||||||
|
g_slice_free (KeySequence, seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
im_context_multipress_class_init (GtkImContextMultipressClass *klass)
|
||||||
|
{
|
||||||
|
GtkIMContextClass *im_context_class;
|
||||||
|
|
||||||
/* Set this so we can use it later: */
|
/* Set this so we can use it later: */
|
||||||
gtk_im_context_multipress_parent_class = g_type_class_peek_parent (klass);
|
im_context_multipress_parent_class = g_type_class_peek_parent (klass);
|
||||||
|
|
||||||
/* Specify our vfunc implementations: */
|
/* Specify our vfunc implementations: */
|
||||||
im_context_class = GTK_IM_CONTEXT_CLASS (klass);
|
im_context_class = GTK_IM_CONTEXT_CLASS (klass);
|
||||||
im_context_class->filter_keypress = vfunc_filter_keypress;
|
im_context_class->filter_keypress = &vfunc_filter_keypress;
|
||||||
im_context_class->reset = vfunc_reset;
|
im_context_class->reset = &vfunc_reset;
|
||||||
im_context_class->get_preedit_string = vfunc_get_preedit_string;
|
im_context_class->get_preedit_string = &vfunc_get_preedit_string;
|
||||||
|
|
||||||
G_OBJECT_CLASS (klass)->finalize = gtk_im_context_multipress_finalize;
|
G_OBJECT_CLASS (klass)->finalize = &im_context_multipress_finalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtk_im_context_multipress_init (GtkImContextMultipress *self)
|
im_context_multipress_init (GtkImContextMultipress *self)
|
||||||
{
|
{
|
||||||
gtk_im_context_multipress_load_config (self);
|
self->key_sequences = g_hash_table_new_full (&g_direct_hash, &g_direct_equal,
|
||||||
|
NULL, &key_sequence_free);
|
||||||
|
load_config (self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gtk_im_context_multipress_finalize (GObject *obj)
|
im_context_multipress_finalize (GObject *obj)
|
||||||
{
|
{
|
||||||
GtkImContextMultipress *self = GTK_IM_CONTEXT_MULTIPRESS (obj);
|
GtkImContextMultipress *self;
|
||||||
|
|
||||||
|
self = GTK_IM_CONTEXT_MULTIPRESS (obj);
|
||||||
|
|
||||||
/* Release the configuration data: */
|
/* Release the configuration data: */
|
||||||
|
if (self->key_sequences != NULL)
|
||||||
/* Free each item: */
|
|
||||||
gsize i = 0;
|
|
||||||
for (i = 0; i < self->key_sequences_count; ++i)
|
|
||||||
{
|
|
||||||
KeySequence *item = self->key_sequences[i];
|
|
||||||
|
|
||||||
/* Free the array of strings in the item: */
|
|
||||||
/* This is only for null-terminated arrays: g_strfreev(item->characters); */
|
|
||||||
gsize i = 0;
|
|
||||||
for (i = 0; i < item->characters_length; ++i)
|
|
||||||
{
|
{
|
||||||
gchar *str = item->characters[i];
|
g_hash_table_destroy (self->key_sequences);
|
||||||
g_free (str);
|
self->key_sequences = NULL;
|
||||||
item->characters[i] = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free (item->characters);
|
(*im_context_multipress_parent_class->finalize) (obj);
|
||||||
item->characters = NULL;
|
|
||||||
item->characters_length = 0;
|
|
||||||
|
|
||||||
/* Free the item itself: */
|
|
||||||
g_free (item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the array of pointers: */
|
|
||||||
g_free (self->key_sequences);
|
|
||||||
|
|
||||||
self->key_sequences = NULL;
|
|
||||||
self->key_sequences_count = 0;
|
|
||||||
|
|
||||||
|
|
||||||
gtk_im_context_multipress_parent_class->finalize (obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GtkIMContext
|
GtkIMContext *
|
||||||
*gtk_im_context_multipress_new (void)
|
gtk_im_context_multipress_new (void)
|
||||||
{
|
{
|
||||||
return (GtkIMContext *)g_object_new (GTK_TYPE_IM_CONTEXT_MULTIPRESS, NULL);
|
return (GtkIMContext *)g_object_new (GTK_TYPE_IM_CONTEXT_MULTIPRESS, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lookup a compose sequence for the key press, from the table. The result is
|
|
||||||
* a null-terminated array of gchar*. It should not be freed by the caller.
|
|
||||||
*/
|
|
||||||
static KeySequence *
|
|
||||||
lookup_characters (GtkImContextMultipress *multipress_context, guint keypress)
|
|
||||||
{
|
|
||||||
|
|
||||||
/* Find the matching KeySequence, so that the caller can look at the possible
|
|
||||||
* characters for this keypress: */
|
|
||||||
gsize i = 0;
|
|
||||||
for (i = 0; i < multipress_context->key_sequences_count; ++i)
|
|
||||||
{
|
|
||||||
KeySequence *item = multipress_context->key_sequences[i];
|
|
||||||
|
|
||||||
/* Just compare the first item, to match the keyval: */
|
|
||||||
if (keypress == item->key_press)
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cancel_automatic_timeout_commit (GtkImContextMultipress *multipress_context)
|
cancel_automatic_timeout_commit (GtkImContextMultipress *multipress_context)
|
||||||
{
|
{
|
||||||
/* printf("debug: cancelling timeout\n"); */
|
|
||||||
|
|
||||||
if (multipress_context->timeout_id)
|
if (multipress_context->timeout_id)
|
||||||
g_source_remove (multipress_context->timeout_id);
|
g_source_remove (multipress_context->timeout_id);
|
||||||
|
|
||||||
@ -284,9 +199,7 @@ on_timeout (gpointer data)
|
|||||||
{
|
{
|
||||||
GtkImContextMultipress *multipress_context;
|
GtkImContextMultipress *multipress_context;
|
||||||
|
|
||||||
GDK_THREADS_ENTER();
|
GDK_THREADS_ENTER ();
|
||||||
|
|
||||||
/* printf("debug: on_timeout\n"); */
|
|
||||||
|
|
||||||
multipress_context = GTK_IM_CONTEXT_MULTIPRESS (data);
|
multipress_context = GTK_IM_CONTEXT_MULTIPRESS (data);
|
||||||
|
|
||||||
@ -296,7 +209,7 @@ on_timeout (gpointer data)
|
|||||||
|
|
||||||
multipress_context->timeout_id = 0;
|
multipress_context->timeout_id = 0;
|
||||||
|
|
||||||
GDK_THREADS_LEAVE();
|
GDK_THREADS_LEAVE ();
|
||||||
|
|
||||||
return FALSE; /* don't call me again */
|
return FALSE; /* don't call me again */
|
||||||
}
|
}
|
||||||
@ -304,283 +217,224 @@ on_timeout (gpointer data)
|
|||||||
static gboolean
|
static gboolean
|
||||||
vfunc_filter_keypress (GtkIMContext *context, GdkEventKey *event)
|
vfunc_filter_keypress (GtkIMContext *context, GdkEventKey *event)
|
||||||
{
|
{
|
||||||
GtkIMContextClass *parent;
|
GtkIMContextClass *parent;
|
||||||
GtkImContextMultipress *multipress_context;
|
GtkImContextMultipress *multipress_context;
|
||||||
|
|
||||||
/* printf("debug: vfunc_filter_keypress:\n"); */
|
|
||||||
|
|
||||||
multipress_context = GTK_IM_CONTEXT_MULTIPRESS (context);
|
multipress_context = GTK_IM_CONTEXT_MULTIPRESS (context);
|
||||||
|
|
||||||
if (event->type == GDK_KEY_PRESS)
|
if (event->type == GDK_KEY_PRESS)
|
||||||
{
|
|
||||||
KeySequence *possible = NULL;
|
|
||||||
|
|
||||||
/* printf("debug: multipress_context->compose_count=%d\n", multipress_context->compose_count); */
|
|
||||||
|
|
||||||
/* Check whether the current key is the same as previously entered, because
|
|
||||||
* if it is not then we should accept the previous one, and start a new
|
|
||||||
* character. */
|
|
||||||
if (multipress_context->compose_count > 0
|
|
||||||
&& multipress_context->key_last_entered != event->keyval)
|
|
||||||
{
|
|
||||||
/* Accept the previously chosen character: */
|
|
||||||
if (multipress_context->tentative_match)
|
|
||||||
{
|
|
||||||
/* This wipes the compose_count and key_last_entered. */
|
|
||||||
accept_character (multipress_context, multipress_context->tentative_match);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Decide what character this key press would choose: */
|
|
||||||
if (!passthrough_enabled_for_window (event->window))
|
|
||||||
possible = lookup_characters (multipress_context, event->keyval); /* Not to be freed. */
|
|
||||||
|
|
||||||
if (possible)
|
|
||||||
{
|
{
|
||||||
if (multipress_context->compose_count == 0)
|
KeySequence *possible;
|
||||||
g_signal_emit_by_name (multipress_context, "preedit-start");
|
|
||||||
|
|
||||||
/* Check whether we are at the end of a compose sequence, with no more possible characters: */
|
/* Check whether the current key is the same as previously entered, because
|
||||||
/* Cycle back to the start if necessary: */
|
* if it is not then we should accept the previous one, and start a new
|
||||||
if (multipress_context->compose_count >= possible->characters_length)
|
* character. */
|
||||||
multipress_context->compose_count = 0;
|
if (multipress_context->compose_count > 0
|
||||||
|
&& multipress_context->key_last_entered != event->keyval
|
||||||
|
&& multipress_context->tentative_match != NULL)
|
||||||
|
{
|
||||||
|
/* Accept the previously chosen character. This wipes
|
||||||
|
* the compose_count and key_last_entered. */
|
||||||
|
accept_character (multipress_context,
|
||||||
|
multipress_context->tentative_match);
|
||||||
|
}
|
||||||
|
|
||||||
/* Store the last key pressed in the compose sequence. */
|
/* Decide what character this key press would choose: */
|
||||||
multipress_context->key_last_entered = event->keyval;
|
possible = g_hash_table_lookup (multipress_context->key_sequences,
|
||||||
|
GUINT_TO_POINTER (event->keyval));
|
||||||
|
if (possible != NULL)
|
||||||
|
{
|
||||||
|
if (multipress_context->compose_count == 0)
|
||||||
|
g_signal_emit_by_name (multipress_context, "preedit-start");
|
||||||
|
|
||||||
/* Get the possible match for this number of presses of the key.
|
/* Check whether we are at the end of a compose sequence, with no more
|
||||||
* compose_count starts at 1, so that 0 can mean not composing. */
|
* possible characters. Cycle back to the start if necessary. */
|
||||||
multipress_context->tentative_match = possible->characters[multipress_context->compose_count++];
|
if (multipress_context->compose_count >= possible->n_characters)
|
||||||
|
multipress_context->compose_count = 0;
|
||||||
|
|
||||||
/* Indicate the current possible character.
|
/* Store the last key pressed in the compose sequence. */
|
||||||
* This will cause our vfunc_get_preedit_string() vfunc to be called,
|
multipress_context->key_last_entered = event->keyval;
|
||||||
* which will provide the current possible character for the user to see.
|
|
||||||
*/
|
|
||||||
g_signal_emit_by_name (multipress_context, "preedit-changed");
|
|
||||||
|
|
||||||
/* Cancel any outstanding timeout, so we can start the timer again: */
|
|
||||||
cancel_automatic_timeout_commit (multipress_context);
|
|
||||||
|
|
||||||
/* Create a timeout that will cause the currently chosen character to be committed,
|
/* Get the possible match for this number of presses of the key.
|
||||||
* if nothing happens for a certain amount of time:
|
* compose_count starts at 1, so that 0 can mean not composing. */
|
||||||
*/
|
multipress_context->tentative_match =
|
||||||
multipress_context->timeout_id = g_timeout_add_seconds
|
possible->characters[multipress_context->compose_count++];
|
||||||
(AUTOMATIC_COMPOSE_TIMEOUT, on_timeout, multipress_context);
|
|
||||||
|
|
||||||
return TRUE; /* TRUE means that the event was handled. */
|
/* Indicate the current possible character. This will cause our
|
||||||
|
* vfunc_get_preedit_string() vfunc to be called, which will provide
|
||||||
|
* the current possible character for the user to see. */
|
||||||
|
g_signal_emit_by_name (multipress_context, "preedit-changed");
|
||||||
|
|
||||||
|
/* Cancel any outstanding timeout, so we can start the timer again: */
|
||||||
|
cancel_automatic_timeout_commit (multipress_context);
|
||||||
|
|
||||||
|
/* Create a timeout that will cause the currently chosen character to
|
||||||
|
* be committed, if nothing happens for a certain amount of time: */
|
||||||
|
multipress_context->timeout_id =
|
||||||
|
g_timeout_add_seconds (AUTOMATIC_COMPOSE_TIMEOUT,
|
||||||
|
&on_timeout, multipress_context);
|
||||||
|
|
||||||
|
return TRUE; /* key handled */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
guint32 keyval_uchar;
|
||||||
|
|
||||||
|
/* Just accept all other keypresses directly, but commit the
|
||||||
|
* current preedit content first. */
|
||||||
|
if (multipress_context->compose_count > 0
|
||||||
|
&& multipress_context->tentative_match != NULL)
|
||||||
|
{
|
||||||
|
accept_character (multipress_context,
|
||||||
|
multipress_context->tentative_match);
|
||||||
|
}
|
||||||
|
keyval_uchar = gdk_keyval_to_unicode (event->keyval);
|
||||||
|
|
||||||
|
/* Convert to a string for accept_character(). */
|
||||||
|
if (keyval_uchar != 0)
|
||||||
|
{
|
||||||
|
/* max length of UTF-8 sequence = 6 + 1 for NUL termination */
|
||||||
|
gchar keyval_utf8[7];
|
||||||
|
gint length;
|
||||||
|
|
||||||
|
length = g_unichar_to_utf8 (keyval_uchar, keyval_utf8);
|
||||||
|
keyval_utf8[length] = '\0';
|
||||||
|
|
||||||
|
accept_character (multipress_context, keyval_utf8);
|
||||||
|
|
||||||
|
return TRUE; /* key handled */
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
guint32 keyval_uchar;
|
|
||||||
|
|
||||||
/*
|
parent = (GtkIMContextClass *)im_context_multipress_parent_class;
|
||||||
* Just accept all other keypresses directly, but commit the
|
|
||||||
* current preedit content first.
|
|
||||||
*/
|
|
||||||
if (multipress_context->compose_count > 0 && multipress_context->tentative_match)
|
|
||||||
accept_character (multipress_context, multipress_context->tentative_match);
|
|
||||||
|
|
||||||
keyval_uchar = gdk_keyval_to_unicode (event->keyval);
|
/* The default implementation just returns FALSE, but it is generally
|
||||||
/*
|
* a good idea to call the base class implementation: */
|
||||||
* Convert to a string, because that's what accept_character() needs.
|
|
||||||
*/
|
|
||||||
if (keyval_uchar)
|
|
||||||
{
|
|
||||||
gchar keyval_utf8[7]; /* max length of UTF-8 sequence + 1 for NUL termination */
|
|
||||||
gint length;
|
|
||||||
|
|
||||||
length = g_unichar_to_utf8 (keyval_uchar, keyval_utf8);
|
|
||||||
keyval_utf8[length] = '\0';
|
|
||||||
|
|
||||||
accept_character (multipress_context, keyval_utf8);
|
|
||||||
|
|
||||||
return TRUE; /* key handled */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The default implementation just returns FALSE,
|
|
||||||
* but it is generally a good idea to call the base class implementation:
|
|
||||||
*/
|
|
||||||
parent = (GtkIMContextClass *)gtk_im_context_multipress_parent_class;
|
|
||||||
if (parent->filter_keypress)
|
if (parent->filter_keypress)
|
||||||
return parent->filter_keypress (context, event);
|
return (*parent->filter_keypress) (context, event);
|
||||||
else
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vfunc_reset (GtkIMContext *context)
|
vfunc_reset (GtkIMContext *context)
|
||||||
{
|
{
|
||||||
GtkImContextMultipress *multipress_context = GTK_IM_CONTEXT_MULTIPRESS (context);
|
clear_compose_buffer (GTK_IM_CONTEXT_MULTIPRESS (context));
|
||||||
|
|
||||||
clear_compose_buffer (multipress_context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
static void
|
|
||||||
vfunc_get_preedit_string (GtkIMContext *context,
|
vfunc_get_preedit_string (GtkIMContext *context,
|
||||||
gchar **str,
|
gchar **str,
|
||||||
PangoAttrList **attrs,
|
PangoAttrList **attrs,
|
||||||
gint *cursor_pos)
|
gint *cursor_pos)
|
||||||
{
|
{
|
||||||
/* printf("debug: get_preedit_string:\n"); */
|
|
||||||
|
|
||||||
GtkImContextMultipress *multipress_context = GTK_IM_CONTEXT_MULTIPRESS (context);
|
|
||||||
|
|
||||||
/* Show the user what character he will get if he accepts: */
|
|
||||||
gsize len_bytes = 0;
|
gsize len_bytes = 0;
|
||||||
gsize len_utf8_chars = 0;
|
gsize len_utf8_chars = 0;
|
||||||
if (str)
|
|
||||||
{
|
|
||||||
if (multipress_context->tentative_match)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
printf("debug: vfunc_get_preedit_string(): tentative_match != NULL\n");
|
|
||||||
printf("debug: vfunc_get_preedit_string(): tentative_match=%s\n", multipress_context->tentative_match);
|
|
||||||
*/
|
|
||||||
*str = g_strdup (multipress_context->tentative_match);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* *str can never be NULL - that crashes the caller, which doesn't check for it: */
|
|
||||||
*str = g_strdup("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*str)
|
/* Show the user what character he will get if he accepts: */
|
||||||
|
if (str != NULL)
|
||||||
{
|
{
|
||||||
len_utf8_chars = g_utf8_strlen (*str, -1); /* For the cursor pos, which seems need to be UTF-8 characters (GtkEntry clamps it.) */
|
const gchar *match;
|
||||||
len_bytes = strlen (*str); /* The number of bytes, not the number of UTF-8 characters. For the PangoAttribute. */
|
|
||||||
|
match = GTK_IM_CONTEXT_MULTIPRESS (context)->tentative_match;
|
||||||
|
|
||||||
|
if (match == NULL)
|
||||||
|
match = ""; /* *str must not be NUL */
|
||||||
|
|
||||||
|
len_bytes = strlen (match); /* byte count */
|
||||||
|
len_utf8_chars = g_utf8_strlen (match, len_bytes); /* character count */
|
||||||
|
|
||||||
|
*str = g_strndup (match, len_bytes);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* Underline it, to show the user that he is in compose mode: */
|
/* Underline it, to show the user that he is in compose mode: */
|
||||||
if (attrs)
|
if (attrs != NULL)
|
||||||
{
|
{
|
||||||
*attrs = pango_attr_list_new ();
|
*attrs = pango_attr_list_new ();
|
||||||
|
|
||||||
if (len_bytes)
|
if (len_bytes > 0)
|
||||||
{
|
{
|
||||||
PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
|
PangoAttribute *attr;
|
||||||
|
|
||||||
|
attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
|
||||||
attr->start_index = 0;
|
attr->start_index = 0;
|
||||||
attr->end_index = len_bytes;
|
attr->end_index = len_bytes;
|
||||||
pango_attr_list_insert (*attrs, attr);
|
pango_attr_list_insert (*attrs, attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor_pos)
|
if (cursor_pos)
|
||||||
*cursor_pos = len_utf8_chars;
|
*cursor_pos = len_utf8_chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
/* Open the configuration file and fill in the key_sequences hash table
|
||||||
gtk_im_context_multipress_load_config (GtkImContextMultipress *self)
|
* with key/character-list pairs taken from the [keys] group of the file.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
load_config (GtkImContextMultipress *self)
|
||||||
{
|
{
|
||||||
/* Open the configuration file: */
|
|
||||||
GKeyFile *key_file;
|
GKeyFile *key_file;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
GArray *array;
|
gchar **keys;
|
||||||
gboolean found;
|
gsize n_keys = 0;
|
||||||
guint key_suffix_num = 0;
|
gsize i;
|
||||||
gboolean keep_looking = TRUE;
|
|
||||||
|
|
||||||
key_file = g_key_file_new ();
|
key_file = g_key_file_new ();
|
||||||
found = g_key_file_load_from_file (key_file, CONFIGURATION_FILENAME, G_KEY_FILE_NONE, &error);
|
|
||||||
if (!found || error)
|
if (!g_key_file_load_from_file (key_file, CONFIGURATION_FILENAME,
|
||||||
{
|
G_KEY_FILE_NONE, &error))
|
||||||
if (error)
|
|
||||||
{
|
{
|
||||||
g_warning ("Error while trying to open the %s configuration file: %s", CONFIGURATION_FILENAME, error->message);
|
g_warning ("Error while trying to open the %s configuration file: %s",
|
||||||
|
CONFIGURATION_FILENAME, error->message);
|
||||||
g_error_free (error);
|
g_error_free (error);
|
||||||
error = NULL;
|
g_key_file_free (key_file);
|
||||||
|
return;
|
||||||
/*debug_output_possible_data_dirs();*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_key_file_free (key_file);
|
keys = g_key_file_get_keys (key_file, "keys", &n_keys, &error);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Get data from the file:
|
if (error != NULL)
|
||||||
* Each KP_* key should have a value consiting of ;-separated values: */
|
|
||||||
|
|
||||||
array = g_array_sized_new (FALSE /* Not zero_terminated */, TRUE /* clear */,
|
|
||||||
sizeof(KeySequence*), 10 /* reserved size */);
|
|
||||||
|
|
||||||
/* Look at each KP_* key in sequence, until there are no more KP_* keys: */
|
|
||||||
while (keep_looking)
|
|
||||||
{
|
|
||||||
gchar* key_name;
|
|
||||||
gchar** values;
|
|
||||||
gsize length_values = 0;
|
|
||||||
|
|
||||||
key_name = g_strdup_printf ("KP_%d", key_suffix_num);
|
|
||||||
values = g_key_file_get_string_list (key_file, "keys", key_name, &length_values, &error);
|
|
||||||
if (error)
|
|
||||||
{
|
{
|
||||||
/* Only show the warning if this was the first key. It's OK to fail when trying to find subsequent keys: */
|
g_warning ("Error while trying to read the %s configuration file: %s",
|
||||||
if (key_suffix_num == 0)
|
CONFIGURATION_FILENAME, error->message);
|
||||||
{
|
|
||||||
g_warning ("Error while trying to read key values from the configuration file: %s", error->message);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_error_free (error);
|
g_error_free (error);
|
||||||
error = NULL;
|
g_key_file_free (key_file);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values)
|
for (i = 0; i < n_keys; ++i)
|
||||||
{
|
{
|
||||||
/* printf("debug: No values found for key %s\n", key_name); */
|
KeySequence *seq;
|
||||||
keep_looking = FALSE; /* This must be the last in the array of keys. */
|
guint keyval;
|
||||||
/* debug_output_possible_config_keys(key_file); */
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
KeySequence *key_sequence;
|
|
||||||
GArray *array_characters;
|
|
||||||
gsize value_index = 0;
|
|
||||||
|
|
||||||
key_sequence = g_new0 (KeySequence, 1);
|
keyval = gdk_keyval_from_name (keys[i]);
|
||||||
g_array_append_val (array, key_sequence);
|
|
||||||
|
|
||||||
array_characters = g_array_sized_new (FALSE /* Not zero_terminated */, TRUE /* clear */,
|
if (keyval == GDK_VoidSymbol)
|
||||||
sizeof(gchar*), 10 /* reserved size */);
|
|
||||||
|
|
||||||
for (value_index = 0; value_index < length_values; ++value_index)
|
|
||||||
{
|
|
||||||
gchar *value;
|
|
||||||
gchar *value_copy;
|
|
||||||
|
|
||||||
value = values[value_index];
|
|
||||||
|
|
||||||
if (value_index == 0)
|
|
||||||
{
|
{
|
||||||
g_assert (strlen(value) > 0);
|
g_warning ("Error while trying to read the %s configuration file: "
|
||||||
|
"invalid key name \"%s\"",
|
||||||
key_sequence->key_press = g_utf8_get_char (value);
|
CONFIGURATION_FILENAME, keys[i]);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
value_copy = g_strdup (value);
|
seq = g_slice_new (KeySequence);
|
||||||
g_array_append_val (array_characters, value_copy);
|
seq->characters = g_key_file_get_string_list (key_file, "keys", keys[i],
|
||||||
|
&seq->n_characters, &error);
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
g_warning ("Error while trying to read the %s configuration file: %s",
|
||||||
|
CONFIGURATION_FILENAME, error->message);
|
||||||
|
g_error_free (error);
|
||||||
|
error = NULL;
|
||||||
|
g_slice_free (KeySequence, seq);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* printf("debug: Key=%s, value=%s\n", key_name, value); */
|
/* Ownership of the KeySequence is taken over by the hash table */
|
||||||
}
|
g_hash_table_insert (self->key_sequences, GUINT_TO_POINTER (keyval), seq);
|
||||||
|
|
||||||
g_strfreev (values);
|
|
||||||
|
|
||||||
key_sequence->characters_length = array_characters->len;
|
|
||||||
key_sequence->characters = (gchar **)g_array_free(array_characters,
|
|
||||||
FALSE /* Don't free items - return a real array of them. */);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g_free (key_name);
|
g_strfreev (keys);
|
||||||
++key_suffix_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_key_file_free (key_file);
|
g_key_file_free (key_file);
|
||||||
|
|
||||||
self->key_sequences_count = array->len;
|
|
||||||
self->key_sequences = (KeySequence **)g_array_free (array,
|
|
||||||
FALSE /* Don't free items - return a real array of them. */);
|
|
||||||
|
|
||||||
/* debug_output_key_sequences_array(self); */
|
|
||||||
}
|
}
|
||||||
|
@ -30,26 +30,25 @@ G_BEGIN_DECLS
|
|||||||
#define GTK_IS_IM_CONTEXT_MULTIPRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IM_CONTEXT_MULTIPRESS))
|
#define GTK_IS_IM_CONTEXT_MULTIPRESS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IM_CONTEXT_MULTIPRESS))
|
||||||
#define GTK_IM_CONTEXT_MULTIPRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_IM_CONTEXT_MULTIPRESS, GtkImContextMultipressClass))
|
#define GTK_IM_CONTEXT_MULTIPRESS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_IM_CONTEXT_MULTIPRESS, GtkImContextMultipressClass))
|
||||||
|
|
||||||
|
|
||||||
typedef struct _KeySequence KeySequence;
|
|
||||||
|
|
||||||
typedef struct _GtkImContextMultipress GtkImContextMultipress;
|
typedef struct _GtkImContextMultipress GtkImContextMultipress;
|
||||||
|
|
||||||
/** This input method allows multi-press character input, like that found on mobile phones.
|
/* This input method allows multi-press character input, like that found on
|
||||||
|
* mobile phones.
|
||||||
*
|
*
|
||||||
* This is based on GtkImContextSimple, which allows "compose" based on sequences of characters.
|
* This is based on GtkImContextSimple, which allows "compose" based on
|
||||||
* But instead the character sequences are defined by lists of characters for a key,
|
* sequences of characters. But instead the character sequences are defined
|
||||||
* so that repeated pressing of the same key can cycle through the possible output characters,
|
* by lists of characters for a key, so that repeated pressing of the same key
|
||||||
* with automatic choosing of the character after a time delay.
|
* can cycle through the possible output characters, with automatic choosing
|
||||||
*/
|
* of the character after a time delay.
|
||||||
|
*/
|
||||||
struct _GtkImContextMultipress
|
struct _GtkImContextMultipress
|
||||||
{
|
{
|
||||||
|
/*< private >*/
|
||||||
GtkIMContext parent;
|
GtkIMContext parent;
|
||||||
|
|
||||||
/* Sequence information, loading from the configuration file: */
|
/* Sequence information, loaded from the configuration file: */
|
||||||
KeySequence** key_sequences; /* Built when we read the config file. Not null-terminated. */
|
GHashTable* key_sequences;
|
||||||
gsize key_sequences_count; /* Number of KeySequence struct instances. */
|
gsize dummy; /* ABI-preserving placeholder */
|
||||||
|
|
||||||
|
|
||||||
/* The last character entered so far during a compose.
|
/* The last character entered so far during a compose.
|
||||||
* If this is NULL then we are not composing yet.
|
* If this is NULL then we are not composing yet.
|
||||||
@ -59,12 +58,11 @@ struct _GtkImContextMultipress
|
|||||||
/* The position of the compose in the possible sequence.
|
/* The position of the compose in the possible sequence.
|
||||||
* For instance, this is 2 if aa has been pressed to show b (from abc0).
|
* For instance, this is 2 if aa has been pressed to show b (from abc0).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
guint compose_count;
|
guint compose_count;
|
||||||
guint timeout_id;
|
guint timeout_id;
|
||||||
|
|
||||||
/* The character(s) that will be used if it the current character(s) is accepted: */
|
/* The character(s) that will be used if it the current character(s) is accepted: */
|
||||||
const gchar* tentative_match;
|
const gchar *tentative_match;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
# Configuration File for the GTK+ Multipress Input Method, Copyright 2007 Openismus GmbH
|
# Example configuration file for the GTK+ Multipress Input Method
|
||||||
|
# Authored by Openismus GmbH, 2009.
|
||||||
#
|
#
|
||||||
# The first character is the key that you press repeatedly to get that character and the following characters.
|
# This file follows the GKeyFile format. On the left of the equal sign goes
|
||||||
# Each character is separated by ;
|
# the key that you press repeatedly to iterate through the text items listed
|
||||||
# Use \ to escape characters - for instance, \; for ";" or \\ for "\"
|
# on the right-hand side. The list items are separated by semicolons ";" and
|
||||||
|
# consist of one or more characters each. The backslash "\" is used to escape
|
||||||
|
# characters; for instance "\;" for a literal semicolon.
|
||||||
#
|
#
|
||||||
# This is the Glib GKeyFile format.
|
# The example configuration below imitates the behavior of a standard mobile
|
||||||
|
# phone by a major manufacturer, with German language setting.
|
||||||
# The § item is to test non-ASCII keycodes.
|
|
||||||
|
|
||||||
[keys]
|
[keys]
|
||||||
|
KP_1 = .;,;?;!;';";1;-;(;);@;/;:;_
|
||||||
KP_0 = .;,;:;/
|
KP_2 = a;b;c;2;ä;à;á;ã;â;å;æ;ç
|
||||||
KP_1= a;b;c;ä;2
|
KP_3 = d;e;f;3;è;é;ë;ê;ð
|
||||||
KP_2 = d;e;f;3
|
KP_4 = g;h;i;4;ì;í;î;ï
|
||||||
KP_3 = g;h;i;4
|
KP_5 = j;k;l;5;£
|
||||||
KP_4 = j;k;l;5
|
KP_6 = m;n;o;6;ö;ò;ó;ô;õ;ø;ñ
|
||||||
KP_5 = m;n;o;ö;6
|
KP_7 = p;q;r;s;7;ß;$
|
||||||
KP_6 = p;q;r;s;7
|
KP_8 = t;u;v;8;ü;ù;ú;û
|
||||||
KP_7 = t;u;v;ü;8
|
KP_9 = w;x;y;z;9;ý;þ
|
||||||
KP_8 = w;x;y;z;9
|
KP_0 = \s;0
|
||||||
KP_9 = §;X;Y;Z
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user