/* * gtkimcontextime.c * 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, see . * */ /* * 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 */ #ifdef GTK_DISABLE_DEPRECATED #undef GTK_DISABLE_DEPRECATED #endif #include "config.h" #include "gtkimcontextime.h" #include "imm-extra.h" #include "gdk/gdkkeysyms-compat.h" #include "gdk/win32/gdkwin32.h" #include "gdk/win32/gdkprivate-win32.h" #include "gdk/gdkkeysyms.h" #include "gdk/gdkinternals.h" #include /* avoid warning */ #ifdef STRICT # undef STRICT # include # ifndef STRICT # define STRICT 1 # endif #else /* STRICT */ # include #endif /* STRICT */ /* Determines what happens when focus is lost while preedit is in process. */ typedef enum { /* Preedit is committed. */ GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT, /* Preedit is discarded. */ GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD, /* Preedit follows the cursor (that means it will appear in the widget * that receives the focus) */ GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW, } GtkWin32IMEFocusBehavior; struct _GtkIMContextIMEPrivate { /* When pretend_empty_preedit is set to TRUE, * gtk_im_context_ime_get_preedit_string() will return an empty string * instead of the actual content of ImmGetCompositionStringW(). * * This is necessary because GtkEntry expects the preedit buffer to be * cleared before commit() is called, otherwise it leads to an assertion * failure in Pango. However, since we emit the commit() signal while * handling the WM_IME_COMPOSITION message, the IME buffer will be non-empty, * so we temporarily set this flag while emmiting the appropriate signals. * * See also: * https://bugzilla.gnome.org/show_bug.cgi?id=787142 * https://gitlab.gnome.org/GNOME/gtk/commit/c255ba68fc2c918dd84da48a472e7973d3c00b03 */ gboolean pretend_empty_preedit; GtkWin32IMEFocusBehavior focus_behavior; }; /* 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); 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) { 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->commit_string = NULL; context_ime->priv = g_malloc0 (sizeof (GtkIMContextIMEPrivate)); context_ime->priv->focus_behavior = GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT; } 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 != NULL) gtk_im_context_ime_set_client_window (context, NULL); if (G_OBJECT_CLASS (parent_class)->dispose) G_OBJECT_CLASS (parent_class)->dispose (obj); } static void gtk_im_context_ime_finalize (GObject *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; GdkWindow *toplevel = NULL; g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context)); context_ime = GTK_IM_CONTEXT_IME (context); if (client_window != NULL && !GDK_IS_WINDOW (client_window)) { g_warning ("client_window is not a GdkWindow!"); client_window = NULL; } if (client_window != NULL) { toplevel = gdk_window_get_toplevel (client_window); if (GDK_IS_WINDOW (toplevel)) { HWND hwnd = gdk_win32_window_get_impl_hwnd (toplevel); HIMC himc = ImmGetContext (hwnd); if (himc) { context_ime->opened = ImmGetOpenStatus (himc); ImmReleaseContext (hwnd, himc); } else { context_ime->opened = FALSE; } } else { g_warning ("Could not find toplevel window."); } } else if (context_ime->focus) { gtk_im_context_ime_focus_out (context); } context_ime->client_window = client_window; context_ime->toplevel = toplevel; if (client_window) g_return_if_fail (GDK_IS_WINDOW (context_ime->toplevel)); } static gboolean gtk_im_context_ime_filter_keypress (GtkIMContext *context, GdkEventKey *event) { GtkIMContextIME *context_ime; GdkEventPrivate *event_priv; gchar *utf8; g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context), FALSE); g_return_val_if_fail (event, FALSE); context_ime = GTK_IM_CONTEXT_IME (context); g_return_val_if_fail (gdk_event_is_allocated ((GdkEvent*)event), FALSE); event_priv = (GdkEventPrivate*) event; if (event_priv->translation_len == 0) return FALSE; utf8 = g_utf16_to_utf8 (event_priv->translation, event_priv->translation_len, NULL, NULL, NULL); g_signal_emit_by_name (context_ime, "commit", utf8); g_free (utf8); return TRUE; } static void gtk_im_context_ime_reset (GtkIMContext *context) { GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context); HWND hwnd; HIMC himc; if (!GDK_IS_WINDOW (context_ime->client_window)) return; g_return_if_fail (GDK_IS_WINDOW (context_ime->toplevel)); hwnd = gdk_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return; ImmNotifyIME (himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); if (context_ime->preediting) { 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 kind, gint *pos_ret) { gunichar2 *utf16str = NULL; glong size; gchar *utf8str = NULL; HWND hwnd; HIMC himc; gint pos = 0; GError *error = NULL; if (pos_ret) *pos_ret = 0; if (!GDK_IS_WINDOW (context_ime->toplevel)) return g_strdup (""); hwnd = gdk_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return g_strdup (""); size = ImmGetCompositionStringW (himc, kind, NULL, 0); if (size > 0) { utf16str = g_malloc (size); ImmGetCompositionStringW (himc, kind, utf16str, size); utf8str = g_utf16_to_utf8 (utf16str, size / sizeof (gunichar2), NULL, NULL, &error); if (error) { g_warning ("%s", error->message); g_error_free (error); } if (pos_ret) { pos = ImmGetCompositionStringW (himc, GCS_CURSORPOS, NULL, 0); if (pos < 0 || size < pos) { g_warning ("ImmGetCompositionString: " "Invalid cursor position!"); pos = 0; } } } if (!utf8str) { utf8str = g_strdup (""); pos = 0; } if (pos_ret) *pos_ret = pos; ImmReleaseContext (hwnd, himc); g_free (utf16str); return utf8str; } static PangoAttrList * get_pango_attr_list (GtkIMContextIME *context_ime, const gchar *utf8str) { PangoAttrList *attrs = pango_attr_list_new (); HWND hwnd; HIMC himc; guint8 *buf = NULL; if (!GDK_IS_WINDOW (context_ime->client_window)) return attrs; g_return_val_if_fail (GDK_IS_WINDOW (context_ime->toplevel), attrs); hwnd = gdk_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return attrs; if (context_ime->preediting) { const gchar *schr = utf8str, *echr; 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 = ImmGetCompositionStringW (himc, GCS_COMPATTR, NULL, 0); buf = g_malloc (len); ImmGetCompositionStringW (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. * Using the wide-char API, this value is same with UTF-8 offset. */ epos = g_utf8_pointer_to_offset (utf8str, echr); /* * 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; } } } ImmReleaseContext (hwnd, himc); g_free(buf); 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); if (!context_ime->focus || context_ime->priv->pretend_empty_preedit) utf8str = g_strdup (""); else utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, &pos); if (attrs) *attrs = get_pango_attr_list (context_ime, utf8str); if (str) *str = utf8str; else g_free (utf8str); 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 = NULL; GtkWidget *widget = NULL; HWND hwnd; HIMC himc; if (!GDK_IS_WINDOW (context_ime->client_window)) return; /* swtich current context */ context_ime->focus = TRUE; toplevel = gdk_window_get_toplevel (context_ime->client_window); if (!GDK_IS_WINDOW (toplevel)) { g_warning ("Could not find toplevel window."); context_ime->toplevel = NULL; context_ime->opened = FALSE; return; } hwnd = gdk_win32_window_get_impl_hwnd (toplevel); himc = ImmGetContext (hwnd); if (!himc) return; gdk_window_add_filter (toplevel, gtk_im_context_ime_message_filter, context_ime); /* trace reparenting (probably no need) */ gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget); if (GTK_IS_WIDGET (widget)) { g_signal_connect (widget, "hierarchy-changed", G_CALLBACK (cb_client_widget_hierarchy_changed), context_ime); } else { /* warning? */ } context_ime->opened = ImmGetOpenStatus (himc); switch (context_ime->priv->focus_behavior) { case GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT: case GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD: gtk_im_context_ime_reset (context); break; case GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW: { gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, NULL); if (utf8str != NULL && strlen(utf8str) > 0) { context_ime->preediting = TRUE; gtk_im_context_ime_set_cursor_location (context, NULL); g_signal_emit_by_name (context, "preedit-start"); g_signal_emit_by_name (context, "preedit-changed"); } g_free (utf8str); } break; } /* clean */ ImmReleaseContext (hwnd, himc); } static void gtk_im_context_ime_focus_out (GtkIMContext *context) { GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context); GtkWidget *widget = NULL; gboolean was_preediting; if (!GDK_IS_WINDOW (context_ime->client_window)) return; was_preediting = context_ime->preediting; context_ime->opened = FALSE; context_ime->preediting = FALSE; context_ime->focus = FALSE; switch (context_ime->priv->focus_behavior) { case GTK_WIN32_IME_FOCUS_BEHAVIOR_COMMIT: if (was_preediting) { gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_COMPSTR, NULL); context_ime->priv->pretend_empty_preedit = TRUE; g_signal_emit_by_name (context, "preedit-changed"); g_signal_emit_by_name (context, "preedit-end"); g_signal_emit_by_name (context, "commit", utf8str); g_signal_emit_by_name (context, "preedit-start"); g_signal_emit_by_name (context, "preedit-changed"); context_ime->priv->pretend_empty_preedit = FALSE; g_free (utf8str); } /* fallthrough */ case GTK_WIN32_IME_FOCUS_BEHAVIOR_DISCARD: gtk_im_context_ime_reset (context); /* Callbacks triggered by im_context_ime_reset() could set the focus back to our context. In that case, we want to exit here. */ if (context_ime->focus) return; break; case GTK_WIN32_IME_FOCUS_BEHAVIOR_FOLLOW: break; } /* 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 filter */ if (GDK_IS_WINDOW (context_ime->toplevel)) { gdk_window_remove_filter (context_ime->toplevel, gtk_im_context_ime_message_filter, context_ime); } if (was_preediting) { g_signal_emit_by_name (context, "preedit-changed"); g_signal_emit_by_name (context, "preedit-end"); } } 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; guint scale; 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_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return; scale = gdk_window_get_scale_factor (context_ime->client_window); get_window_position (context_ime->client_window, &wx, &wy); cf.dwStyle = CFS_POINT; cf.ptCurrentPos.x = (wx + context_ime->cursor_location.x) * scale; cf.ptCurrentPos.y = (wy + context_ime->cursor_location.y) * scale; 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) { HWND hwnd; HIMC himc; hwnd = gdk_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return; /* FIXME: What to do? */ ImmReleaseContext (hwnd, himc); } } static void gtk_im_context_ime_set_preedit_font (GtkIMContext *context) { GtkIMContextIME *context_ime; GtkWidget *widget = NULL; HWND hwnd; HIMC himc; HKL ime = GetKeyboardLayout (0); const gchar *lang; gunichar wc; PangoContext *pango_context; PangoFont *font; LOGFONT *logfont; GtkStyleContext *style; PangoFontDescription *font_desc; 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_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return; /* set font */ pango_context = gtk_widget_get_pango_context (widget); if (!pango_context) goto ERROR_OUT; /* Try to make sure we use a font that actually can show the * language in question. */ switch (PRIMARYLANGID (LOWORD (ime))) { case LANG_JAPANESE: lang = "ja"; break; case LANG_KOREAN: lang = "ko"; break; case LANG_CHINESE: switch (SUBLANGID (LOWORD (ime))) { case SUBLANG_CHINESE_TRADITIONAL: lang = "zh_TW"; break; case SUBLANG_CHINESE_SIMPLIFIED: lang = "zh_CN"; break; case SUBLANG_CHINESE_HONGKONG: lang = "zh_HK"; break; case SUBLANG_CHINESE_SINGAPORE: lang = "zh_SG"; break; case SUBLANG_CHINESE_MACAU: lang = "zh_MO"; break; default: lang = "zh"; break; } break; default: lang = ""; break; } style = gtk_widget_get_style_context (widget); gtk_style_context_save (style); gtk_style_context_set_state (style, GTK_STATE_FLAG_NORMAL); gtk_style_context_get (style, gtk_style_context_get_state (style), "font", &font_desc, NULL); gtk_style_context_restore (style); if (lang[0]) { /* We know what language it is. Look for a character, any * character, that language needs. */ PangoLanguage *pango_lang = pango_language_from_string (lang); PangoFontset *fontset = pango_context_load_fontset (pango_context, font_desc, pango_lang); gunichar *sample = g_utf8_to_ucs4 (pango_language_get_sample_string (pango_lang), -1, NULL, NULL, NULL); wc = 0x4E00; /* In all CJK languages? */ if (sample != NULL) { int i; for (i = 0; sample[i]; i++) if (g_unichar_iswide (sample[i])) { wc = sample[i]; break; } g_free (sample); } font = pango_fontset_get_font (fontset, wc); g_object_unref (fontset); } else font = pango_context_load_font (pango_context, font_desc); if (!font) goto ERROR_OUT; logfont = pango_win32_font_logfont (font); if (logfont) ImmSetCompositionFont (himc, logfont); g_object_unref (font); 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; g_return_val_if_fail (GDK_IS_WINDOW (context_ime->toplevel), retval); hwnd = gdk_win32_window_get_impl_hwnd (context_ime->toplevel); himc = ImmGetContext (hwnd); if (!himc) return retval; switch (msg->message) { case WM_IME_COMPOSITION: { gint wx = 0, wy = 0; CANDIDATEFORM cf; gint scale = gdk_window_get_scale_factor (context_ime->client_window); get_window_position (context_ime->client_window, &wx, &wy); cf.dwIndex = 0; cf.dwStyle = CFS_EXCLUDE; cf.ptCurrentPos.x = (wx + context_ime->cursor_location.x) * scale; cf.ptCurrentPos.y = (wy + context_ime->cursor_location.y) * scale; cf.rcArea.left = (wx + context_ime->cursor_location.x) * scale; cf.rcArea.right = (wx + context_ime->cursor_location.x + context_ime->cursor_location.width) * scale; cf.rcArea.top = (wy + context_ime->cursor_location.y) * scale; cf.rcArea.bottom = (wy + context_ime->cursor_location.y + context_ime->cursor_location.height) * scale; ImmSetCandidateWindow (himc, &cf); if ((msg->lParam & GCS_COMPSTR)) g_signal_emit_by_name (context, "preedit-changed"); if (msg->lParam & GCS_RESULTSTR) { gchar *utf8str = get_utf8_preedit_string (context_ime, GCS_RESULTSTR, NULL); if (utf8str) { context_ime->priv->pretend_empty_preedit = TRUE; g_signal_emit_by_name (context, "preedit-changed"); g_signal_emit_by_name (context, "preedit-end"); g_signal_emit_by_name (context, "commit", utf8str); g_signal_emit_by_name (context, "preedit-start"); g_signal_emit_by_name (context, "preedit-changed"); context_ime->priv->pretend_empty_preedit = FALSE; retval = TRUE; } 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); 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->client_window) g_return_if_fail (new_toplevel != NULL); 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); } /* 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); } context_ime->toplevel = new_toplevel; }