]> Pileus Git - ~andy/gtk/commitdiff
Add the multipress input method. (#417446, Johannes Schmid, Murray
authorMatthias Clasen <mclasen@redhat.com>
Sun, 18 Mar 2007 05:34:24 +0000 (05:34 +0000)
committerMatthias Clasen <matthiasc@src.gnome.org>
Sun, 18 Mar 2007 05:34:24 +0000 (05:34 +0000)
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

ChangeLog
modules/input/Makefile.am
modules/input/README.multipress [new file with mode: 0644]
modules/input/gtkimcontextmultipress.c [new file with mode: 0644]
modules/input/gtkimcontextmultipress.h [new file with mode: 0644]
modules/input/im-multipress.conf [new file with mode: 0644]
modules/input/immultipress.c [new file with mode: 0644]

index 4bcb3fd25e1135a9775c3d7c519f63c897382b1a..2ec7c88b02bc423a13b15b037eb156fd99c33dab 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+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
+
 2007-03-17  Chris Wilson  <chris@chris-wilson.co.uk>
 
        * gdk/x11/gdkvisual-x11.c (_gdk_visual_init):
index a7c5165a7cc17f4e5202d6d3a03450adc2d902b8..998735a8b8f12573b4926c4916663ea0367ecef4 100644 (file)
@@ -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 (file)
index 0000000..d44161d
--- /dev/null
@@ -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 (file)
index 0000000..6b880ed
--- /dev/null
@@ -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 (file)
index 0000000..a7d94d9
--- /dev/null
@@ -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 (file)
index 0000000..6296793
--- /dev/null
@@ -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 (file)
index 0000000..e53ccdd
--- /dev/null
@@ -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;
+}