mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-10 02:40:11 +00:00
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
This commit is contained in:
parent
905a167f1e
commit
f326c1e9ec
10
ChangeLog
10
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):
|
||||
|
@ -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)
|
||||
|
42
modules/input/README.multipress
Normal file
42
modules/input/README.multipress
Normal file
@ -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
|
628
modules/input/gtkimcontextmultipress.c
Normal file
628
modules/input/gtkimcontextmultipress.c
Normal file
@ -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); */
|
||||
}
|
85
modules/input/gtkimcontextmultipress.h
Normal file
85
modules/input/gtkimcontextmultipress.h
Normal file
@ -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 */
|
23
modules/input/im-multipress.conf
Normal file
23
modules/input/im-multipress.conf
Normal file
@ -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
|
||||
|
70
modules/input/immultipress.c
Normal file
70
modules/input/immultipress.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user