diff options
author | Matthias Clasen <mclasen@redhat.com> | 2007-03-18 05:34:24 +0000 |
---|---|---|
committer | Matthias Clasen <matthiasc@src.gnome.org> | 2007-03-18 05:34:24 +0000 |
commit | f326c1e9ec6ed808656f1de5523c4a15a601747e (patch) | |
tree | c511ec5617486327fcc080999f4012032fa93b09 /modules/input | |
parent | 905a167f1ef2c881f51540482e9d2a32773c5a7a (diff) | |
download | gtk+-f326c1e9ec6ed808656f1de5523c4a15a601747e.tar.gz |
Add the multipress input method. (#417446, Johannes Schmid, Murray
2007-03-18 Matthias Clasen <mclasen@redhat.com>
* modules/input/gtkimcontextmultipress.[hc]:
* modules/input/im-multipress.conf:
* modules/input/immultipress.c:
* modules/input/README.multipress: Add the multipress input
method. (#417446, Johannes Schmid, Murray Cumming)
* modules/input/Makefile.am: Glue
svn path=/trunk/; revision=17541
Diffstat (limited to 'modules/input')
-rw-r--r-- | modules/input/Makefile.am | 12 | ||||
-rw-r--r-- | modules/input/README.multipress | 42 | ||||
-rw-r--r-- | modules/input/gtkimcontextmultipress.c | 628 | ||||
-rw-r--r-- | modules/input/gtkimcontextmultipress.h | 85 | ||||
-rw-r--r-- | modules/input/im-multipress.conf | 23 | ||||
-rw-r--r-- | modules/input/immultipress.c | 70 |
6 files changed, 860 insertions, 0 deletions
diff --git a/modules/input/Makefile.am b/modules/input/Makefile.am index a7c5165a7c..998735a8b8 100644 --- a/modules/input/Makefile.am +++ b/modules/input/Makefile.am @@ -91,6 +91,17 @@ if USE_WIN32 IM_IME_MODULE=im-ime.la endif +multipress_defs = -DMULTIPRESS_LOCALEDIR=\""$(mplocaledir)"\" -DMULTIPRESS_CONFDIR=\""$(DESTDIR)$(sysconfdir)/gtk-2.0"\" +im_multipress_la_CPPFLAGS = $(multipress_defs) +im_multipress_la_LDFLAGS = -rpath $(moduledir) -avoid-version -module $(no_undefined) +im_multipress_la_SOURCES = \ + gtkimcontextmultipress.c \ + gtkimcontextmultipress.h \ + immultipress.c + +imconffiledir = $(DESTDIR)$(sysconfdir)/gtk-2.0 +dist_imconffile_DATA = im-multipress.conf + if CROSS_COMPILING RUN_QUERY_IMMODULES_TEST=false else @@ -132,6 +143,7 @@ module_LTLIBRARIES = \ im-ti-er.la \ im-ti-et.la \ im-viqr.la \ + im-multipress.la \ $(IM_IME_MODULE) gtk.immodules: Makefile.am $(module_LTLIBRARIES) diff --git a/modules/input/README.multipress b/modules/input/README.multipress new file mode 100644 index 0000000000..d44161d878 --- /dev/null +++ b/modules/input/README.multipress @@ -0,0 +1,42 @@ +*** Introduction + +This is a GTK+ input method which allows text entry via the multi-press method, +as on a mobile phone. When this has been installed, you can choose the "Multipress" +menu item from the "Input Methods" submenu when right-clicking in a GTK+ text entry +area. + +For instance: +- press a to get a, then wait 1 second for the character to be accepted. +or +- press dd to get e, then wait 1 second for the character to be accepted. +or +- press ad to get ad, then wait 1 second for the d character to be accepted. + + +*** Configuration + +Edit the im-multipress.conf to define the keypresses needed to input particular characters. +This file is in GKeyFile-format, and contains explanatory comments. + + +*** Per-widget deactivation + +When the input method is active (either by choosing it from the context menu, or +by defining the default language as "*" in src/im-multipress.c), the multipress +behaviour can be turned off for individual widgets, like so: + + g_object_set_data(G_OBJECT(yourwidget), "multipress-passthrough-flag", GINT_TO_POINTER(1)); + + +For a C++ gtkmm project, you could make a convenience function to do this. For instance: + + void multipress_deactivate(Gtk::Widget& widget) + { + g_object_set_data(G_OBJECT(widget.gobj()), "multipress-passthrough-flag", GINT_TO_POINTER(1)); + } + +*** Contact + +Please contact Openismus for assistance with this input method. You can email murrayc@openismus.com + +Copyright 2006-2007, Openismus GmbH diff --git a/modules/input/gtkimcontextmultipress.c b/modules/input/gtkimcontextmultipress.c new file mode 100644 index 0000000000..6b880ed8b7 --- /dev/null +++ b/modules/input/gtkimcontextmultipress.c @@ -0,0 +1,628 @@ +/* Copyright (C) 2006 Openismus GmbH + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gtkimcontextmultipress.h" +#include <gtk/gtkimcontext.h> +#include <gtk/gtkimmodule.h> +#include <gtk/gtkimcontext.h> +#include <gtk/gtkwidget.h> +#include <gtk/gtkwindow.h> +#include <gdk/gdkwindow.h> +#include <gdk/gdkkeysyms.h> /* For GDK_A, etc. */ +#include <glib.h> /* For GKeyFile */ +#include <glib-object.h> +#include <string.h> /* For memset() */ + +#define AUTOMATIC_COMPOSE_TIMEOUT 1 /* seconds */ + +/* Just the last part of the name, not the whole path: */ +#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 inputed by pressing a particular key repeatedly. + * Each row has one key (such as GDK_A), and an array of characters, such as 'a'. + */ +struct _KeySequence +{ + gunichar key_press; /* Such as 'a' (== GDK_a) */ + gchar** characters; /* Array of strings. */ + gsize characters_length; /* size of the array of strings. */ +}; + + +static void gtk_im_context_multipress_class_init (GtkImContextMultipressClass *klass); +static void gtk_im_context_multipress_init (GtkImContextMultipress *self); +static void gtk_im_context_multipress_finalize (GObject * obj); + +static void gtk_im_context_multipress_load_config (GtkImContextMultipress *self); + +static GObjectClass* gtk_im_context_multipress_parent_class = NULL; +static GType gtk_im_multi_press_im_context_type = 0; + +/** Notice that we have a *_register_type(GTypeModule*) function instead of a *_get_type() function, + * because we must use g_type_module_register_type(), providing the GTypeModule* that was provided to im_context_init(). + * That is also why we are not using G_DEFINE_TYPE(). + */ +void +gtk_im_context_multipress_register_type (GTypeModule* type_module) +{ + if (gtk_im_multi_press_im_context_type == 0) + { + static const GTypeInfo im_context_multipress_info = + { + sizeof(GtkImContextMultipressClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gtk_im_context_multipress_class_init, + NULL, + NULL, + sizeof(GtkImContextMultipress), + 0, + (GInstanceInitFunc) gtk_im_context_multipress_init, + 0, + }; + + gtk_im_multi_press_im_context_type = + g_type_module_register_type (type_module, + GTK_TYPE_IM_CONTEXT, + "GtkImContextMultipress", + &im_context_multipress_info, 0); + } +} + +GType +gtk_im_context_multipress_get_type(void) +{ + g_assert (gtk_im_multi_press_im_context_type != 0); + return gtk_im_multi_press_im_context_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 +gtk_im_context_multipress_class_init (GtkImContextMultipressClass *klass) +{ + GtkIMContextClass* im_context_class; + + /* Set this so we can use it later: */ + gtk_im_context_multipress_parent_class = g_type_class_peek_parent(klass); + + /* Specify our vfunc implementations: */ + im_context_class = GTK_IM_CONTEXT_CLASS (klass); + im_context_class->filter_keypress = vfunc_filter_keypress; + im_context_class->reset = vfunc_reset; + im_context_class->get_preedit_string = vfunc_get_preedit_string; + + G_OBJECT_CLASS(klass)->finalize = gtk_im_context_multipress_finalize; +} + +static void +gtk_im_context_multipress_init (GtkImContextMultipress *self) +{ + gtk_im_context_multipress_load_config (self); +} + +static void +gtk_im_context_multipress_finalize (GObject * obj) +{ + GtkImContextMultipress *self = gtk_im_context_multipress(obj); + + /* Release the configuration data: */ + + /* 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_free (str); + item->characters[i] = NULL; + } + + g_free(item->characters); + 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; + + + G_OBJECT_CLASS (gtk_im_context_multipress_parent_class)->finalize(obj); +} + + +GtkIMContext +*gtk_im_context_multipress_new (void) +{ + 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 an 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 +cancel_automatic_timeout_commit (GtkImContextMultipress *multipress_context) +{ + /* printf("debug: cancelling timeout\n"); */ + + if (multipress_context->timeout_id) + g_source_remove (multipress_context->timeout_id); /* This function cancels timeouts, idle handlers, etc. */ + + multipress_context->timeout_id = 0; +} + + +/** Clear the compose buffer, so we are ready to compose the next character. + */ +static void +clear_compose_buffer (GtkImContextMultipress *multipress_context) +{ + multipress_context->key_last_entered = 0; + multipress_context->compose_count = 0; + + multipress_context->tentative_match = NULL; + cancel_automatic_timeout_commit(multipress_context); +} + +/** Finish composing, provide the character, and clear our compose buffer. + */ +static void +accept_character (GtkImContextMultipress *multipress_context, const gchar* characters) +{ + /* printf("debug: accepting character: %c\n", (char)character); */ + + cancel_automatic_timeout_commit (multipress_context); + + /* Accept the character: */ + + /* Clear the compose buffer, so we are ready to compose the next character. */ + clear_compose_buffer (multipress_context); + + /* We must also signal that the preedit has changed, or we will still see the old + preedit from the composing of the character that we just committed, hanging around after the cursor. + */ + g_signal_emit_by_name (multipress_context, "preedit_changed"); + + /* Provide a character to GTK+: */ + g_signal_emit_by_name (multipress_context, "commit", characters); + + /* Note that if we emit preedit_changed after commit, + * there's a segfault/invalid-write with GtkTextView in gtk_text_layout_free_line_display(), when destroying a PangoLayout + * (this can also be avoided by not using any pango attributes in get_preedit_string(). + */ +} + +static gboolean +on_timeout (gpointer data) +{ + GtkImContextMultipress* multipress_context; + + GDK_THREADS_ENTER(); + + /* printf("debug: on_timeout\n"); */ + + multipress_context = gtk_im_context_multipress(data); + + /* A certain amount of time has passed, + * so we will assume that the user really wants the currently chosen character: + */ + accept_character (multipress_context, multipress_context->tentative_match); + + multipress_context->timeout_id = 0; + + GDK_THREADS_LEAVE(); + + return FALSE; /* Don't call this callback again. We only need to call it once. */ +} + +static gboolean +vfunc_filter_keypress (GtkIMContext *context, GdkEventKey *event) +{ + GtkIMContextClass* parent; + GtkImContextMultipress* multipress_context; + + /* printf("debug: vfunc_filter_keypress:\n"); */ + + multipress_context = gtk_im_context_multipress (context); + + /* We only care about key releases: */ + if (event->type == GDK_KEY_RELEASE) + { + 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) && (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) + { + /* Check whether we are at the end of a compose sequence, with no more possible characters: */ + /* Cycle back to the start if necessary: */ + if (multipress_context->compose_count >= possible->characters_length) + { + clear_compose_buffer (multipress_context); + return vfunc_filter_keypress (context, event); + } + + /* Store the last key pressed in the compose sequence. */ + multipress_context->key_last_entered = event->keyval; + ++(multipress_context->compose_count); + + /* Get the possible match for this number of presses of the key: */ + multipress_context->tentative_match = possible->characters[multipress_context->compose_count -1]; /* compose_count starts at 1, so that 0 can mean not composing. */ + + /* 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: + */ + /* g_timeout_add_seconds is only available since glib 2.14: multipress_context->timeout_id = g_timeout_add_seconds(AUTOMATIC_COMPOSE_TIMEOUT, on_timeout, multipress_context); */ + multipress_context->timeout_id = g_timeout_add (AUTOMATIC_COMPOSE_TIMEOUT * 1000, on_timeout, multipress_context); + } + else + { + /*printf("debug: no possible characters for keyval=%d (char=%c), (GDK_a=%d)\n", + event->keyval, event->keyval, GDK_a);*/ + + /*Just accept all other keypresses directly:*/ + /* Convert to a string, because that's what accept_character() and the commit signal need: */ + guint32 keyval_uchar = gdk_keyval_to_unicode (event->keyval); + + if (keyval_uchar) + { + /* TODO: The delete key does not seem to be handled when we do this. + * danielk: Yes, that's normal and it's not the only one. I'll get to that later. */ + gchar keyval_utf8[7]; /* max length of UTF-8 sequence + 1 for NUL termination */ + gint length = g_unichar_to_utf8 (keyval_uchar, keyval_utf8); + keyval_utf8[length] = '\0'; /* g_unichar_to_utf8() does not add null termination. */ + + accept_character (multipress_context, keyval_utf8); + } + } + + return TRUE; /* TRUE means that the event was handled. */ + } + else if (event->type == GDK_KEY_PRESS) + { + if ((multipress_context->compose_count > 0)) + { + /* Handle backspace: + * This should cause any preedit for a current composition to vanish. + * Otherwise, the user sees both the preedit character and the previous character be deleted. + * If not composing, then the normal behaviour will be seen - the previous character is deleted. + */ + switch (event->keyval) + { + case GDK_BackSpace: + { + /* Stop composing: */ + clear_compose_buffer(multipress_context); + g_signal_emit_by_name (multipress_context, "preedit_changed"); + return TRUE; /* TRUE means that we handled this keypress. */ + } + /* Handle return and tab: + * This should cause the currently-composed character to be accepted. + */ + case GDK_Return: + case GDK_KP_Enter: + case GDK_ISO_Enter: + case GDK_Tab: + case GDK_KP_Tab: + case GDK_ISO_Left_Tab: + { + accept_character (multipress_context, multipress_context->tentative_match); + + return TRUE; /* TRUE means that we handled this keypress. */ + } + } + } + } + + /* 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) + return parent->filter_keypress (context, event); + else + return FALSE; +} + +static void +vfunc_reset (GtkIMContext *context) +{ + GtkImContextMultipress *multipress_context = gtk_im_context_multipress (context); + + clear_compose_buffer (multipress_context); +} + + +static void +vfunc_get_preedit_string (GtkIMContext *context, + gchar **str, + PangoAttrList **attrs, + 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_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) + { + len_utf8_chars = g_utf8_strlen (*str, -1); /* For the cursor pos, which seems need to be UTF-8 characters (GtkEntry clamps it.) */ + len_bytes = strlen (*str); /* The number of bytes, not the number of UTF-8 characters. For the PangoAttribute. */ + } + } + + /* Underline it, to show the user that he is in compose mode: */ + if (attrs) + { + *attrs = pango_attr_list_new (); + + if (len_bytes) + { + PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + attr->start_index = 0; + attr->end_index = len_bytes; + pango_attr_list_insert (*attrs, attr); + } + } + + if (cursor_pos) + *cursor_pos = len_utf8_chars; +} + +static void +gtk_im_context_multipress_load_config (GtkImContextMultipress *self) +{ + /* Open the configuration file: */ + GKeyFile* key_file; + GError* error = NULL; + GArray* array; + gboolean found; + guint key_suffix_num = 0; + gboolean keep_looking = TRUE; + + 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 (error) + { + g_warning ("Error while trying to open the %s configuration file: %s", CONFIGURATION_FILENAME, error->message); + g_error_free (error); + error = NULL; + + /*debug_output_possible_data_dirs();*/ + } + + g_key_file_free (key_file); + return; + } + + /* Get data from the file: + * 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: */ + if (key_suffix_num == 0) + { + g_warning ("Error while trying to read key values from the configuration file: %s", error->message); + } + + g_error_free (error); + error = NULL; + } + + if (!values) + { + /* printf("debug: No values found for key %s\n", key_name); */ + keep_looking = FALSE; /* This must be the last in the array of keys. */ + /* debug_output_possible_config_keys(key_file); */ + } + else + { + KeySequence* key_sequence; + GArray* array_characters; + gsize value_index = 0; + + key_sequence = g_new0 (KeySequence, 1); + g_array_append_val (array, key_sequence); + + array_characters = g_array_sized_new (FALSE /* Not zero_terminated */, TRUE /* clear */, + 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); + + key_sequence->key_press = g_utf8_get_char (value); + } + + value_copy = g_strdup (value); + g_array_append_val (array_characters, value_copy); + + /* printf("debug: Key=%s, value=%s\n", key_name, value); */ + } + + 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); + ++key_suffix_num; + } + + 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); */ +} diff --git a/modules/input/gtkimcontextmultipress.h b/modules/input/gtkimcontextmultipress.h new file mode 100644 index 0000000000..a7d94d9f66 --- /dev/null +++ b/modules/input/gtkimcontextmultipress.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2006 Openismus GmbH + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __GTK_IM_CONTEXT_MULTIPRESS_H +#define __GTK_IM_CONTEXT_MULTIPRESS_H + +#include <gtk/gtkimcontext.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_IM_CONTEXT_MULTIPRESS (gtk_im_context_multipress_get_type()) +#define gtk_im_context_multipress(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_IM_CONTEXT_MULTIPRESS, GtkImContextMultipress)) +#define gtk_im_context_multipress_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_IM_CONTEXT_MULTIPRESS, GtkImContextMultipressClass)) +#define GTK_IS_IM_CONTEXT_MULTIPRESS(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_IM_CONTEXT_MULTIPRESS)) +#define GTK_IS_IM_CONTEXT_MULTIPRESS_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IM_CONTEXT_MULTIPRESS)) +#define gtk_im_context_multipress_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_TYPE_IM_CONTEXT_MULTIPRESS, GtkImContextMultipressClass)) + + +typedef struct _KeySequence KeySequence; + +typedef struct _GtkImContextMultipress GtkImContextMultipress; + +/** 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. + * But instead the character sequences are defined by lists of characters for a key, + * so that repeated pressing of the same key can cycle through the possible output characters, + * with automatic choosing of the character after a time delay. + */ +struct _GtkImContextMultipress +{ + GtkIMContext parent; + + /* Sequence information, loading from the configuration file: */ + KeySequence** key_sequences; /* Built when we read the config file. Not null-terminated. */ + gsize key_sequences_count; /* Number of KeySequence struct instances. */ + + + /* The last character entered so far during a compose. + * If this is NULL then we are not composing yet. + */ + guint key_last_entered; + + /* The position of the compose in the possible sequence. + * For instance, this is 2 if aa has been pressed to show b (from abc0). + */ + + guint compose_count; + guint timeout_id; + + /* The character(s) that will be used if it the current character(s) is accepted: */ + const gchar* tentative_match; +}; + + +typedef struct _GtkImContextMultipressClass GtkImContextMultipressClass; + +struct _GtkImContextMultipressClass +{ + GtkIMContextClass parent_class; +}; + +void gtk_im_context_multipress_register_type (GTypeModule* type_module); +GType gtk_im_context_multipress_get_type (void) G_GNUC_CONST; +GtkIMContext *gtk_im_context_multipress_new (void); + +G_END_DECLS + + +#endif /* __GTK_IM_CONTEXT_MULTIPRESS_H */ diff --git a/modules/input/im-multipress.conf b/modules/input/im-multipress.conf new file mode 100644 index 0000000000..6296793a42 --- /dev/null +++ b/modules/input/im-multipress.conf @@ -0,0 +1,23 @@ +# Configuration File for the GTK+ Multipress Input Method, Copyright 2007 Openismus GmbH +# +# The first character is the key that you press repeatedly to get that character and the following characters. +# Each character is separated by ; +# Use \ to escape characters - for instance, \; for ";" or \\ for "\" +# +# This is the Glib GKeyFile format. + +# The § item is to test non-ASCII keycodes. + +[keys] + +KP_0 = .;,;:;/ +KP_1= a;b;c;ä;2 +KP_2 = d;e;f;3 +KP_3 = g;h;i;4 +KP_4 = j;k;l;5 +KP_5 = m;n;o;ö;6 +KP_6 = p;q;r;s;7 +KP_7 = t;u;v;ü;8 +KP_8 = w;x;y;z;9 +KP_9 = §;X;Y;Z + diff --git a/modules/input/immultipress.c b/modules/input/immultipress.c new file mode 100644 index 0000000000..e53ccdd067 --- /dev/null +++ b/modules/input/immultipress.c @@ -0,0 +1,70 @@ +/* Copyright (C) 2006 Openismus GmbH + * + * 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, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "gtkimcontextmultipress.h" +#include <gtk/gtkimmodule.h> /* For GtkIMContextInfo */ +#include <config.h> +#include <glib/gi18n.h> +#include <string.h> /* For strcmp() */ + +#define CONTEXT_ID "multipress" + +/** NOTE: Change the default language from "" to "*" to enable this input method by default for all locales. + */ +static const GtkIMContextInfo info = { + CONTEXT_ID, /* ID */ + N_("Multipress"), /* Human readable name */ + GETTEXT_PACKAGE, /* Translation domain. Defined in configure.ac */ + MULTIPRESS_LOCALEDIR, /* Dir for bindtextdomain (not strictly needed for "gtk+"). Defined in the Makefile.am */ + "" /* Languages for which this module is the default */ +}; + +static const GtkIMContextInfo *info_list[] = { + &info +}; + +void +im_module_init (GTypeModule *module) +{ + gtk_im_context_multipress_register_type(module); +} + +void +im_module_exit (void) +{ +} + +void +im_module_list (const GtkIMContextInfo ***contexts, + int *n_contexts) +{ + *contexts = info_list; + *n_contexts = G_N_ELEMENTS (info_list); +} + +GtkIMContext * +im_module_create (const gchar *context_id) +{ + if (strcmp (context_id, CONTEXT_ID) == 0) + { + GtkIMContext* imcontext = GTK_IM_CONTEXT(g_object_new (GTK_TYPE_IM_CONTEXT_MULTIPRESS, NULL)); + return imcontext; + } + else + return NULL; +} |