diff --git a/ChangeLog b/ChangeLog index 3d05a76c2f..5bedc380dd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2004-08-26 Matthias Clasen + + * modules/input/Makefile.am: Build imime.la on Windows. + + * modules/input/gtkimcontextime.h: + * modules/input/gtkimcontextime.c: + * modules/input/imime.c: + * modules/input/imm-extra.h: Add the IME input method for Win32 + written by Takuro Ashie and Kazuko IWAMOTO. The code was previously + hosted at http://sourceforge.jp/projects/imime. (#135195) + 2004-08-26 Bill Haneman * gtk/gtktreeview: diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 3d05a76c2f..5bedc380dd 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,14 @@ +2004-08-26 Matthias Clasen + + * modules/input/Makefile.am: Build imime.la on Windows. + + * modules/input/gtkimcontextime.h: + * modules/input/gtkimcontextime.c: + * modules/input/imime.c: + * modules/input/imm-extra.h: Add the IME input method for Win32 + written by Takuro Ashie and Kazuko IWAMOTO. The code was previously + hosted at http://sourceforge.jp/projects/imime. (#135195) + 2004-08-26 Bill Haneman * gtk/gtktreeview: diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 3d05a76c2f..5bedc380dd 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,14 @@ +2004-08-26 Matthias Clasen + + * modules/input/Makefile.am: Build imime.la on Windows. + + * modules/input/gtkimcontextime.h: + * modules/input/gtkimcontextime.c: + * modules/input/imime.c: + * modules/input/imm-extra.h: Add the IME input method for Win32 + written by Takuro Ashie and Kazuko IWAMOTO. The code was previously + hosted at http://sourceforge.jp/projects/imime. (#135195) + 2004-08-26 Bill Haneman * gtk/gtktreeview: diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 3d05a76c2f..5bedc380dd 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,14 @@ +2004-08-26 Matthias Clasen + + * modules/input/Makefile.am: Build imime.la on Windows. + + * modules/input/gtkimcontextime.h: + * modules/input/gtkimcontextime.c: + * modules/input/imime.c: + * modules/input/imm-extra.h: Add the IME input method for Win32 + written by Takuro Ashie and Kazuko IWAMOTO. The code was previously + hosted at http://sourceforge.jp/projects/imime. (#135195) + 2004-08-26 Bill Haneman * gtk/gtktreeview: diff --git a/modules/input/Makefile.am b/modules/input/Makefile.am index 70fcab66dd..b234f0b2e6 100644 --- a/modules/input/Makefile.am +++ b/modules/input/Makefile.am @@ -75,6 +75,18 @@ im_ipa_la_LDFLAGS = -rpath $(moduledir) -avoid-version -module $(no_undefined) im_ipa_la_SOURCES = imipa.c im_ipa_la_LIBADD = $(LDADDS) + +im_ime_la_LDFLAGS = -rpath $(moduledir) -avoid-version -module $(no_undefined) +im_ime_la_SOURCES = \ + gtkimcontextime.c \ + gtkimcontextime.h \ + imime.c \ + imm-extra.h +im_ime_la_LIBADD = $(LDADDS) +if PLATFORM_WIN32 +IM_IME_MODULE=im-ime.la +endif + if CROSS_COMPILING RUN_QUERY_IMMODULES_TEST=false else @@ -115,7 +127,8 @@ module_LTLIBRARIES = \ im-thai-broken.la \ im-ti-er.la \ im-ti-et.la \ - im-viqr.la + im-viqr.la \ + $(IM_IME_MODULE) gtk.immodules: Makefile.am $(module_LTLIBRARIES) $(top_builddir)/gtk/gtk-query-immodules-2.0 $(module_LTLIBRARIES) > gtk.immodules diff --git a/modules/input/gtkimcontextime.c b/modules/input/gtkimcontextime.c new file mode 100644 index 0000000000..b441d53c57 --- /dev/null +++ b/modules/input/gtkimcontextime.c @@ -0,0 +1,1103 @@ +/* + * gtkimmoduleime + * Copyright (C) 2003 Takuro Ashie + * Copyright (C) 2003-2004 Kazuki IWAMOTO + * + * 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. + * + * $Id$ + */ + +/* + * Please see the following site for the detail of Windows IME API. + * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/appendix/hh/appendix/imeimes2_35ph.asp + */ + +#include "gtkimcontextime.h" + +#include "imm-extra.h" + +#include +#include +#include + +/* avoid warning */ +#ifdef STRICT +# undef STRICT +# include +# ifndef STRICT +# define STRICT 1 +# endif +#else /* STRICT */ +# include +#endif /* STRICT */ + +/* #define BUFSIZE 4096 */ + +#define FREE_PREEDIT_BUFFER(ctx) \ +{ \ + g_free((ctx)->priv->comp_str); \ + g_free((ctx)->priv->read_str); \ + (ctx)->priv->comp_str = NULL; \ + (ctx)->priv->read_str = NULL; \ + (ctx)->priv->comp_str_len = 0; \ + (ctx)->priv->read_str_len = 0; \ +} + + +struct _GtkIMContextIMEPrivate +{ + /* save IME context when the client window is focused out */ + DWORD conversion_mode; + DWORD sentence_mode; + + LPVOID comp_str; + DWORD comp_str_len; + LPVOID read_str; + DWORD read_str_len; +}; + + +/* GObject class methods */ +static void gtk_im_context_ime_class_init (GtkIMContextIMEClass *class); +static void gtk_im_context_ime_init (GtkIMContextIME *context_ime); +static void gtk_im_context_ime_dispose (GObject *obj); +static void gtk_im_context_ime_finalize (GObject *obj); + +static void gtk_im_context_ime_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_im_context_ime_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkIMContext's virtual functions */ +static void gtk_im_context_ime_set_client_window (GtkIMContext *context, + GdkWindow *client_window); +static gboolean gtk_im_context_ime_filter_keypress (GtkIMContext *context, + GdkEventKey *event); +static void gtk_im_context_ime_reset (GtkIMContext *context); +static void gtk_im_context_ime_get_preedit_string (GtkIMContext *context, + gchar **str, + PangoAttrList **attrs, + gint *cursor_pos); +static void gtk_im_context_ime_focus_in (GtkIMContext *context); +static void gtk_im_context_ime_focus_out (GtkIMContext *context); +static void gtk_im_context_ime_set_cursor_location (GtkIMContext *context, + GdkRectangle *area); +static void gtk_im_context_ime_set_use_preedit (GtkIMContext *context, + gboolean use_preedit); + +/* GtkIMContextIME's private functions */ +static void gtk_im_context_ime_set_preedit_font (GtkIMContext *context, + PangoFont *font); +static GdkFilterReturn + gtk_im_context_ime_message_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data); +static void get_window_position (GdkWindow *win, + gint *x, + gint *y); +static void cb_client_widget_hierarchy_changed (GtkWidget *widget, + GtkWidget *widget2, + GtkIMContextIME *context_ime); + +GType gtk_type_im_context_ime = 0; +static GObjectClass *parent_class; + + +void +gtk_im_context_ime_register_type (GTypeModule *type_module) +{ + static const GTypeInfo im_context_ime_info = { + sizeof (GtkIMContextIMEClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) gtk_im_context_ime_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkIMContextIME), + 0, + (GInstanceInitFunc) gtk_im_context_ime_init, + }; + + gtk_type_im_context_ime = + g_type_module_register_type (type_module, + GTK_TYPE_IM_CONTEXT, + "GtkIMContextIME", &im_context_ime_info, 0); +} + +static void +gtk_im_context_ime_class_init (GtkIMContextIMEClass *class) +{ + GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class); + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + gobject_class->finalize = gtk_im_context_ime_finalize; + gobject_class->dispose = gtk_im_context_ime_dispose; + gobject_class->set_property = gtk_im_context_ime_set_property; + gobject_class->get_property = gtk_im_context_ime_get_property; + + im_context_class->set_client_window = gtk_im_context_ime_set_client_window; + im_context_class->filter_keypress = gtk_im_context_ime_filter_keypress; + im_context_class->reset = gtk_im_context_ime_reset; + im_context_class->get_preedit_string = gtk_im_context_ime_get_preedit_string; + im_context_class->focus_in = gtk_im_context_ime_focus_in; + im_context_class->focus_out = gtk_im_context_ime_focus_out; + im_context_class->set_cursor_location = gtk_im_context_ime_set_cursor_location; + im_context_class->set_use_preedit = gtk_im_context_ime_set_use_preedit; +} + + +static void +gtk_im_context_ime_init (GtkIMContextIME *context_ime) +{ + context_ime->client_window = NULL; + context_ime->toplevel = NULL; + context_ime->use_preedit = TRUE; + context_ime->preediting = FALSE; + context_ime->opened = FALSE; + context_ime->focus = FALSE; + context_ime->cursor_location.x = 0; + context_ime->cursor_location.y = 0; + context_ime->cursor_location.width = 0; + context_ime->cursor_location.height = 0; + + context_ime->priv = g_malloc0 (sizeof (GtkIMContextIMEPrivate)); + context_ime->priv->conversion_mode = 0; + context_ime->priv->sentence_mode = 0; + context_ime->priv->comp_str = NULL; + context_ime->priv->comp_str_len = 0; + context_ime->priv->read_str = NULL; + context_ime->priv->read_str_len = 0; +} + + +static void +gtk_im_context_ime_dispose (GObject *obj) +{ + GtkIMContext *context = GTK_IM_CONTEXT (obj); + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (obj); + + if (context_ime->client_window) + gtk_im_context_ime_set_client_window (context, NULL); + + FREE_PREEDIT_BUFFER (context_ime); + + if (G_OBJECT_CLASS (parent_class)->dispose) + G_OBJECT_CLASS (parent_class)->dispose (obj); +} + + +static void +gtk_im_context_ime_finalize (GObject *obj) +{ + /* GtkIMContext *context = GTK_IM_CONTEXT (obj); */ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (obj); + + g_free (context_ime->priv); + context_ime->priv = NULL; + + if (G_OBJECT_CLASS (parent_class)->finalize) + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + + +static void +gtk_im_context_ime_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (object); + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime)); + + switch (prop_id) + { + default: + break; + } +} + + +static void +gtk_im_context_ime_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (object); + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime)); + + switch (prop_id) + { + default: + break; + } +} + + +GtkIMContext * +gtk_im_context_ime_new (void) +{ + return g_object_new (GTK_TYPE_IM_CONTEXT_IME, NULL); +} + + +static void +gtk_im_context_ime_set_client_window (GtkIMContext *context, + GdkWindow *client_window) +{ + GtkIMContextIME *context_ime; + HWND hwnd = NULL; + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context)); + context_ime = GTK_IM_CONTEXT_IME (context); + + if (client_window) + { + HIMC himc; + + hwnd = GDK_WINDOW_HWND (client_window); + + /* get default ime context */ + himc = ImmGetContext (hwnd); + context_ime->opened = ImmGetOpenStatus (himc); + ImmGetConversionStatus (himc, + &context_ime->priv->conversion_mode, + &context_ime->priv->sentence_mode); + ImmReleaseContext (hwnd, himc); + + } + else if (context_ime->focus) + { + gtk_im_context_ime_focus_out (context); + } + + context_ime->client_window = client_window; +} + + +static gboolean +gtk_im_context_ime_filter_keypress (GtkIMContext *context, + GdkEventKey *event) +{ + GtkIMContextIME *context_ime; + HWND hwnd; + HIMC himc; + gboolean retval = FALSE; + + g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context), FALSE); + g_return_val_if_fail (event, FALSE); + + if (event->type == GDK_KEY_RELEASE) + return retval; + + context_ime = GTK_IM_CONTEXT_IME (context); + if (!context_ime->focus) + return FALSE; + if (!GDK_IS_WINDOW (context_ime->client_window)) + return FALSE; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + + /* FIXME!! event->string is deprecated */ + if (event->string && *event->string + && !g_unichar_iscntrl (g_utf8_get_char_validated (event->string, + strlen (event->string)))) + { + g_signal_emit_by_name (G_OBJECT (context_ime), "commit", event->string); + retval = TRUE; + } + + ImmReleaseContext (hwnd, himc); + + return retval; +} + + +static void +gtk_im_context_ime_reset (GtkIMContext *context) +{ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context); + HWND hwnd; + HIMC himc; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + + if (context_ime->preediting && ImmGetOpenStatus (himc)) + ImmNotifyIME (himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); + + context_ime->preediting = FALSE; + g_signal_emit_by_name (context, "preedit_changed"); + + ImmReleaseContext (hwnd, himc); +} + + +static gchar * +get_utf8_preedit_string (GtkIMContextIME *context_ime, gint *pos_ret) +{ + gchar *utf8str = NULL; + HWND hwnd; + HIMC himc; + gint pos = 0; + + if (pos_ret) + *pos_ret = 0; + + /* + g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime), + g_strdup ("")); + */ + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + + /* shouldn't return NULL */ + g_return_val_if_fail (himc, g_strdup ("")); + + if (context_ime->preediting) + { + gpointer buf; + glong len; + GError *error = NULL; + +#ifdef UNICODE + len = ImmGetCompositionString (himc, GCS_COMPSTR, NULL, 0); + buf = g_malloc (len); + if (len > 0 && buf) + { + ImmGetCompositionString (himc, GCS_COMPSTR, buf, len); + len /= sizeof (gunichar2); + utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + if (pos_ret) + { + pos = ImmGetCompositionString (himc, GCS_CURSORPOS, NULL, 0); + if (pos < 0 || len < pos) + { + g_warning ("ImmGetCompositionString: " + "Invalid cursor position!"); + pos = 0; + } + } + g_free (buf); + } +#else /* not UNICODE */ + len = ImmGetCompositionString (himc, GCS_COMPSTR, NULL, 0); + buf = g_malloc (len); + if (len > 0 && buf) + { + ImmGetCompositionString (himc, GCS_COMPSTR, buf, len); + utf8str = g_locale_to_utf8 (buf, len, NULL, NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + + if (pos_ret) + { + pos = ImmGetCompositionString (himc, GCS_CURSORPOS, NULL, 0); + /* get cursor position by offset */ + if (pos < len && utf8str) + { + gchar *tmpstr; + + tmpstr = g_locale_to_utf8 (buf, pos, NULL, NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + if (tmpstr) + { + pos = g_utf8_strlen (tmpstr, -1); + } + else + { + pos = 0; + } + g_free (tmpstr); + } + else if (pos == len && utf8str) + { + pos = g_utf8_strlen (utf8str, -1); + } + else + { + g_warning ("ImmGetCompositionString: " + "Invalid cursor position!"); + pos = 0; + } + } + g_free (buf); + } +#endif /* not UNICODE */ + } + + if (!utf8str) + { + utf8str = g_strdup (""); + pos = 0; + } + + if (pos_ret) + *pos_ret = pos; + + ImmReleaseContext (hwnd, himc); + + return utf8str; +} + + +static PangoAttrList * +get_pango_attr_list (GtkIMContextIME *context_ime, const gchar *utf8str) +{ + PangoAttrList *attrs = pango_attr_list_new (); + HWND hwnd; + HIMC himc; + + /* g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime), attr); */ + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + + g_return_val_if_fail (himc, attrs); + + if (context_ime->preediting) + { + const gchar *schr = utf8str, *echr; + guint8 *buf; + guint16 f_red, f_green, f_blue, b_red, b_green, b_blue; + glong len, spos = 0, epos, sidx = 0, eidx; + PangoAttribute *attr; + + /* + * get attributes list of IME. + */ + len = ImmGetCompositionString (himc, GCS_COMPATTR, NULL, 0); + buf = g_malloc (len); + ImmGetCompositionString (himc, GCS_COMPATTR, buf, len); + + /* + * schr and echr are pointer in utf8str. + */ + for (echr = g_utf8_next_char (utf8str); *schr != '\0'; + echr = g_utf8_next_char (echr)) + { + /* + * spos and epos are buf(attributes list of IME) position by + * bytes. + * If UNICODE is defined, this value is same with UTF-8 offset. + * If it's not defined, this value is same with bytes position + * of locale encoded preedit string. + * + */ +#ifdef UNICODE + epos = g_utf8_pointer_to_offset (utf8str, echr); +#else /* not UNICODE */ + gchar *tmpstr; + GError *error = NULL; + + epos = spos; + tmpstr = g_locale_from_utf8 (schr, echr - schr, NULL, NULL, &error); + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + if (tmpstr) + { + epos += strlen (tmpstr); + g_free (tmpstr); + } +#endif /* not UNICODE */ + /* + * sidx and eidx are positions in utf8str by bytes. + */ + eidx = echr - utf8str; + + /* + * convert attributes list to PangoAttriute. + */ + if (*echr == '\0' || buf[spos] != buf[epos]) + { + switch (buf[spos]) + { + case ATTR_TARGET_CONVERTED: + attr = pango_attr_underline_new (PANGO_UNDERLINE_DOUBLE); + attr->start_index = sidx; + attr->end_index = eidx; + pango_attr_list_change (attrs, attr); + f_red = f_green = f_blue = 0; + b_red = b_green = b_blue = 0xffff; + break; + case ATTR_TARGET_NOTCONVERTED: + f_red = f_green = f_blue = 0xffff; + b_red = b_green = b_blue = 0; + break; + case ATTR_INPUT_ERROR: + f_red = f_green = f_blue = 0; + b_red = b_green = b_blue = 0x7fff; + break; + default: /* ATTR_INPUT,ATTR_CONVERTED,ATTR_FIXEDCONVERTED */ + attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); + attr->start_index = sidx; + attr->end_index = eidx; + pango_attr_list_change (attrs, attr); + f_red = f_green = f_blue = 0; + b_red = b_green = b_blue = 0xffff; + } + attr = pango_attr_foreground_new (f_red, f_green, f_blue); + attr->start_index = sidx; + attr->end_index = eidx; + pango_attr_list_change (attrs, attr); + attr = pango_attr_background_new (b_red, b_green, b_blue); + attr->start_index = sidx; + attr->end_index = eidx; + pango_attr_list_change (attrs, attr); + + schr = echr; + spos = epos; + sidx = eidx; + } + } + g_free (buf); + } + + ImmReleaseContext (hwnd, himc); + + return attrs; +} + + +static void +gtk_im_context_ime_get_preedit_string (GtkIMContext *context, + gchar **str, + PangoAttrList **attrs, + gint *cursor_pos) +{ + gchar *utf8str = NULL; + gint pos = 0; + GtkIMContextIME *context_ime; + + context_ime = GTK_IM_CONTEXT_IME (context); + + utf8str = get_utf8_preedit_string (context_ime, &pos); + + if (attrs) + *attrs = get_pango_attr_list (context_ime, utf8str); + + if (str) + { + *str = utf8str; + } + else + { + g_free (utf8str); + utf8str = NULL; + } + + if (cursor_pos) + *cursor_pos = pos; +} + + +static void +gtk_im_context_ime_focus_in (GtkIMContext *context) +{ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context); + GdkWindow *toplevel; + GtkWidget *widget = NULL; + HWND hwnd, top_hwnd; + HIMC himc; + + if (!GDK_IS_WINDOW (context_ime->client_window)) + return; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + + toplevel = gdk_window_get_toplevel (context_ime->client_window); + if (GDK_IS_WINDOW (toplevel)) + { + gdk_window_add_filter (toplevel, + gtk_im_context_ime_message_filter, context_ime); + top_hwnd = GDK_WINDOW_HWND (toplevel); + + context_ime->toplevel = toplevel; + } + else + { + g_warning ("gtk_im_context_ime_focus_in(): " + "cannot find toplevel window."); + return; + } + + /* trace reparenting (probably no need) */ + gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget); + if (GTK_IS_WIDGET (widget)) + { + g_signal_connect (G_OBJECT (widget), "hierarchy-changed", + G_CALLBACK (cb_client_widget_hierarchy_changed), + context_ime); + } + else + { + /* warning? */ + } + + /* swtich current context */ + context_ime->focus = TRUE; + + /* restore preedit context */ + ImmSetConversionStatus (himc, + context_ime->priv->conversion_mode, + context_ime->priv->sentence_mode); + + if (context_ime->opened) + { + if (!ImmGetOpenStatus (himc)) + ImmSetOpenStatus (himc, TRUE); + if (context_ime->preediting) + { + ImmSetCompositionString (himc, + SCS_SETSTR, + context_ime->priv->comp_str, + context_ime->priv->comp_str_len, NULL, 0); + FREE_PREEDIT_BUFFER (context_ime); + } + } + + /* clean */ + ImmReleaseContext (hwnd, himc); +} + + +static void +gtk_im_context_ime_focus_out (GtkIMContext *context) +{ + GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context); + GdkWindow *toplevel; + GtkWidget *widget = NULL; + HWND hwnd, top_hwnd; + HIMC himc; + + if (!GDK_IS_WINDOW (context_ime->client_window)) + return; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + + /* save preedit context */ + ImmGetConversionStatus (himc, + &context_ime->priv->conversion_mode, + &context_ime->priv->sentence_mode); + + if (ImmGetOpenStatus (himc)) + { + gboolean preediting = context_ime->preediting; + + if (preediting) + { + FREE_PREEDIT_BUFFER (context_ime); + + context_ime->priv->comp_str_len + = ImmGetCompositionString (himc, GCS_COMPSTR, NULL, 0); + context_ime->priv->comp_str + = g_malloc (context_ime->priv->comp_str_len); + ImmGetCompositionString (himc, GCS_COMPSTR, + context_ime->priv->comp_str, + context_ime->priv->comp_str_len); + + context_ime->priv->read_str_len + = ImmGetCompositionString (himc, GCS_COMPREADSTR, NULL, 0); + context_ime->priv->read_str + = g_malloc (context_ime->priv->read_str_len); + ImmGetCompositionString (himc, GCS_COMPREADSTR, + context_ime->priv->read_str, + context_ime->priv->read_str_len); + } + + ImmSetOpenStatus (himc, FALSE); + + context_ime->opened = TRUE; + context_ime->preediting = preediting; + } + else + { + context_ime->opened = FALSE; + context_ime->preediting = FALSE; + } + + /* remove signal handler */ + gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget); + if (GTK_IS_WIDGET (widget)) + { + g_signal_handlers_disconnect_by_func + (G_OBJECT (widget), + G_CALLBACK (cb_client_widget_hierarchy_changed), context_ime); + } + + /* remove event fileter */ + toplevel = gdk_window_get_toplevel (context_ime->client_window); + if (GDK_IS_WINDOW (toplevel)) + { + gdk_window_remove_filter (toplevel, + gtk_im_context_ime_message_filter, + context_ime); + top_hwnd = GDK_WINDOW_HWND (toplevel); + + context_ime->toplevel = NULL; + } + else + { + g_warning ("gtk_im_context_ime_focus_out(): " + "cannot find toplevel window."); + } + + /* swtich current context */ + context_ime->focus = FALSE; + + /* clean */ + ImmReleaseContext (hwnd, himc); +} + + +static void +gtk_im_context_ime_set_cursor_location (GtkIMContext *context, + GdkRectangle *area) +{ + gint wx = 0, wy = 0; + GtkIMContextIME *context_ime; + COMPOSITIONFORM cf; + HWND hwnd; + HIMC himc; + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context)); + + context_ime = GTK_IM_CONTEXT_IME (context); + if (area) + context_ime->cursor_location = *area; + + if (!context_ime->client_window) + return; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + + get_window_position (context_ime->client_window, &wx, &wy); + cf.dwStyle = CFS_POINT; + cf.ptCurrentPos.x = wx + context_ime->cursor_location.x; + cf.ptCurrentPos.y = wy + context_ime->cursor_location.y; + ImmSetCompositionWindow (himc, &cf); + + ImmReleaseContext (hwnd, himc); +} + + +static void +gtk_im_context_ime_set_use_preedit (GtkIMContext *context, + gboolean use_preedit) +{ + GtkIMContextIME *context_ime; + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context)); + context_ime = GTK_IM_CONTEXT_IME (context); + + context_ime->use_preedit = use_preedit; + if (context_ime->preediting) + { + /* FIXME */ + HWND hwnd; + HIMC himc; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + } +} + + +static void +gtk_im_context_ime_set_preedit_font (GtkIMContext *context, PangoFont *font) +{ + GtkIMContextIME *context_ime; + GtkWidget *widget = NULL; + HWND hwnd; + HIMC himc; + PangoContext *pango_context; + LOGFONT *logfont; + + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context)); + + context_ime = GTK_IM_CONTEXT_IME (context); + if (!context_ime->client_window) + return; + + gdk_window_get_user_data (context_ime->client_window, (gpointer) &widget); + if (!GTK_IS_WIDGET (widget)) + return; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return; + + /* set font */ + pango_context = gtk_widget_get_pango_context (widget); + if (!pango_context) + goto ERROR_OUT; + + if (!font) + font = pango_context_load_font (pango_context, widget->style->font_desc); + if (!font) + goto ERROR_OUT; + + logfont = pango_win32_font_logfont (font); + if (logfont) + ImmSetCompositionFont (himc, logfont); + +ERROR_OUT: + /* clean */ + ImmReleaseContext (hwnd, himc); +} + + +static GdkFilterReturn +gtk_im_context_ime_message_filter (GdkXEvent *xevent, + GdkEvent *event, + gpointer data) +{ + GtkIMContext *context; + GtkIMContextIME *context_ime; + HWND hwnd; + HIMC himc; + MSG *msg = (MSG *) xevent; + GdkFilterReturn retval = GDK_FILTER_CONTINUE; + + g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (data), retval); + + context = GTK_IM_CONTEXT (data); + context_ime = GTK_IM_CONTEXT_IME (data); + if (!context_ime->focus) + return retval; + + hwnd = GDK_WINDOW_HWND (context_ime->client_window); + himc = ImmGetContext (hwnd); + if (!himc) + return retval; + + switch (msg->message) + { + case WM_IME_COMPOSITION: + { + gint wx = 0, wy = 0; + CANDIDATEFORM cf; + + get_window_position (context_ime->client_window, &wx, &wy); + /* FIXME! */ + { + HWND hwnd_top; + POINT pt; + RECT rc; + + hwnd_top = + GDK_WINDOW_HWND (gdk_window_get_toplevel + (context_ime->client_window)); + GetWindowRect (hwnd_top, &rc); + pt.x = wx; + pt.y = wy; + ClientToScreen (hwnd_top, &pt); + wx = pt.x - rc.left; + wy = pt.y - rc.top; + } + cf.dwIndex = 0; + cf.dwStyle = CFS_CANDIDATEPOS; + cf.ptCurrentPos.x = wx + context_ime->cursor_location.x; + cf.ptCurrentPos.y = wy + context_ime->cursor_location.y + + context_ime->cursor_location.height; + ImmSetCandidateWindow (himc, &cf); + + if ((msg->lParam & GCS_COMPSTR)) + g_signal_emit_by_name (context, "preedit_changed"); + + if (msg->lParam & GCS_RESULTSTR) + { + gsize len; + gpointer buf; + gchar *utf8str = NULL; + GError *error = NULL; + + len = ImmGetCompositionString (himc, GCS_RESULTSTR, NULL, 0); + buf = g_alloca (len); + if (len > 0 && buf) + { + ImmGetCompositionString (himc, GCS_RESULTSTR, buf, len); +#ifdef UNICODE + len /= sizeof (gunichar2); + utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error); +#else /* not UNICODE */ + utf8str = g_locale_to_utf8 (buf, len, NULL, NULL, &error); +#endif /* not UNICODE */ + if (error) + { + g_warning ("%s", error->message); + g_error_free (error); + } + } + + if (utf8str) + { + g_signal_emit_by_name (G_OBJECT (context), "commit", utf8str); + g_free (utf8str); + } + } + + if (context_ime->use_preedit) + retval = TRUE; + break; + } + + case WM_IME_STARTCOMPOSITION: + context_ime->preediting = TRUE; + gtk_im_context_ime_set_cursor_location (context, NULL); + g_signal_emit_by_name (context, "preedit_start"); + if (context_ime->use_preedit) + retval = TRUE; + break; + + case WM_IME_ENDCOMPOSITION: + context_ime->preediting = FALSE; + g_signal_emit_by_name (context, "preedit_changed"); + g_signal_emit_by_name (context, "preedit_end"); + if (context_ime->use_preedit) + retval = TRUE; + break; + + case WM_IME_NOTIFY: + switch (msg->wParam) + { + case IMN_SETOPENSTATUS: + context_ime->opened = ImmGetOpenStatus (himc); + gtk_im_context_ime_set_preedit_font (context, NULL); + break; + + default: + break; + } + + default: + break; + } + + ImmReleaseContext (hwnd, himc); + return retval; +} + + +/* + * x and y must be initialized to 0. + */ +static void +get_window_position (GdkWindow *win, gint *x, gint *y) +{ + GdkWindow *parent, *toplevel; + gint wx, wy; + + g_return_if_fail (GDK_IS_WINDOW (win)); + g_return_if_fail (x && y); + + gdk_window_get_position (win, &wx, &wy); + *x += wx; + *y += wy; + parent = gdk_window_get_parent (win); + toplevel = gdk_window_get_toplevel (win); + + if (parent && parent != toplevel) + get_window_position (parent, x, y); +} + + +/* + * probably, this handler isn't needed. + */ +static void +cb_client_widget_hierarchy_changed (GtkWidget *widget, + GtkWidget *widget2, + GtkIMContextIME *context_ime) +{ + GdkWindow *new_toplevel; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime)); + + if (!context_ime->client_window) + return; + if (!context_ime->focus) + return; + + new_toplevel = gdk_window_get_toplevel (context_ime->client_window); + if (context_ime->toplevel == new_toplevel) + return; + + /* remove filter from old toplevel */ + if (GDK_IS_WINDOW (context_ime->toplevel)) + { + gdk_window_remove_filter (context_ime->toplevel, + gtk_im_context_ime_message_filter, + context_ime); + } + else + { + } + + /* add filter to new toplevel */ + if (GDK_IS_WINDOW (new_toplevel)) + { + gdk_window_add_filter (new_toplevel, + gtk_im_context_ime_message_filter, context_ime); + } + else + { + } + + context_ime->toplevel = new_toplevel; +} diff --git a/modules/input/gtkimcontextime.h b/modules/input/gtkimcontextime.h new file mode 100644 index 0000000000..e4d97d9265 --- /dev/null +++ b/modules/input/gtkimcontextime.h @@ -0,0 +1,61 @@ +/* + * gtkimmoduleime + * Copyright (C) 2003 Takuro Ashie + * Copyright (C) 2003 Kazuki IWAMOTO + * + * 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. + * + * $Id$ + */ + +#include + +extern GType gtk_type_im_context_ime; + +#define GTK_TYPE_IM_CONTEXT_IME gtk_type_im_context_ime +#define GTK_IM_CONTEXT_IME(obj) (GTK_CHECK_CAST ((obj), GTK_TYPE_IM_CONTEXT_IME, GtkIMContextIME)) +#define GTK_IM_CONTEXT_IME_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), GTK_TYPE_IM_CONTEXT_IME, GtkIMContextIMEClass)) +#define GTK_IS_IM_CONTEXT_IME(obj) (GTK_CHECK_TYPE ((obj), GTK_TYPE_IM_CONTEXT_IME)) +#define GTK_IS_IM_CONTEXT_IME_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IM_CONTEXT_IME)) +#define GTK_IM_CONTEXT_IME_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), GTK_TYPE_IM_CONTEXT_IME, GtkIMContextIMEClass)) + +typedef struct _GtkIMContextIME GtkIMContextIME; +typedef struct _GtkIMContextIMEPrivate GtkIMContextIMEPrivate; +typedef struct _GtkIMContextIMEClass GtkIMContextIMEClass; + +struct _GtkIMContextIME +{ + GtkIMContext object; + + GdkWindow *client_window; + GdkWindow *toplevel; + guint use_preedit : 1; + guint preediting : 1; + guint opened : 1; + guint focus : 1; + GdkRectangle cursor_location; + + GtkIMContextIMEPrivate *priv; +}; + +struct _GtkIMContextIMEClass +{ + GtkIMContextClass parent_class; +}; + + +void gtk_im_context_ime_register_type (GTypeModule * type_module); +GtkIMContext *gtk_im_context_ime_new (void); diff --git a/modules/input/imime.c b/modules/input/imime.c new file mode 100644 index 0000000000..6100876e5e --- /dev/null +++ b/modules/input/imime.c @@ -0,0 +1,67 @@ +/* + * gtkimmoduleime + * Copyright (C) 2003 Takuro Ashie + * + * 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. + * + * $Id$ + */ + +#include +#include "gtkimcontextime.h" + +#include + +static const GtkIMContextInfo ime_info = { + "win32ime", + "Windows IME", + "gtk+", + "", + "*", +}; + +static const GtkIMContextInfo *info_list[] = { + &ime_info, +}; + +void +im_module_init (GTypeModule * module) +{ + gtk_im_context_ime_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) +{ + g_return_val_if_fail (context_id, NULL); + + if (!strcmp (context_id, "win32ime")) + return g_object_new (GTK_TYPE_IM_CONTEXT_IME, NULL); + else + return NULL; +} diff --git a/modules/input/imm-extra.h b/modules/input/imm-extra.h new file mode 100644 index 0000000000..7bed406f0c --- /dev/null +++ b/modules/input/imm-extra.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* $Id$ */ + + +#include +#include + + +/* these aren't defined in Cygwin's imm.h */ + +#ifndef WM_IME_REQUEST +# define WM_IME_REQUEST 0x0288 +#endif /* WM_IME_REQUEST */ + +#ifndef IMR_COMPOSITIONWINDOW +# define IMR_COMPOSITIONWINDOW 0x0001 +#endif /* IMR_COMPOSITIONWINDOW */ + +#ifndef IMR_CANDIDATEWINDOW +# define IMR_CANDIDATEWINDOW 0x0002 +#endif /* IMR_CANDIDATEWINDOW */ + +#ifndef IMR_COMPOSITIONFONT +# define IMR_COMPOSITIONFONT 0x0003 +#endif /* IMR_COMPOSITIONFONT */ + +#ifndef IMR_RECONVERTSTRING +# define IMR_RECONVERTSTRING 0x0004 +#endif /* IMR_RECONVERTSTRING */ + +#ifndef IMR_CONFIRMRECONVERTSTRING +# define IMR_CONFIRMRECONVERTSTRING 0x0005 +#endif /* IMR_CONFIRMRECONVERTSTRING */ + +#ifndef IMR_QUERYCHARPOSITION +# define IMR_QUERYCHARPOSITION 0x0006 +typedef struct tagIMECHARPOSITION { + DWORD dwSize; + DWORD dwCharPos; + POINT pt; + UINT cLineHeight; + RECT rcDocument; +} IMECHARPOSITION, *PIMECHARPOSITION; +#endif /* IMR_QUERYCHARPOSITION */ + +#ifndef IMR_DOCUMENTFEED +# define IMR_DOCUMENTFEED 0x0007 +#endif /* IMR_DOCUMENTFEED */