/* GTK - The GIMP Toolkit * Copyright (C) 2000 Red Hat Software * Copyright (C) 2003 Motonobu Ichimura * * 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. * * Authors: Motonobu Ichimura * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iiimcf.h" #include "gtkimcontextiiim.h" #include "imaux.h" #include "imswitcher.h" #include "IIIMGdkEventKey.h" #if defined(sun) #include #endif /* sun */ #ifdef DEBUG #define DEBUG_DO(x) (x) #else #define DEBUG_DO(x) #endif #define _IS_DEAD_KEY(keyval,sym) ((keyval) == GDK_dead_ ## sym) #if defined(GDK_dead_hook) && defined(GDK_dead_horn) #define IS_DEAD_KEY(keyval) \ (_IS_DEAD_KEY(keyval, grave) || \ _IS_DEAD_KEY(keyval, acute) || \ _IS_DEAD_KEY(keyval, circumflex) || \ _IS_DEAD_KEY(keyval, tilde) || \ _IS_DEAD_KEY(keyval, macron) || \ _IS_DEAD_KEY(keyval, breve) || \ _IS_DEAD_KEY(keyval, abovedot) || \ _IS_DEAD_KEY(keyval, diaeresis) || \ _IS_DEAD_KEY(keyval, abovering) || \ _IS_DEAD_KEY(keyval, doubleacute) || \ _IS_DEAD_KEY(keyval, caron) || \ _IS_DEAD_KEY(keyval, cedilla) || \ _IS_DEAD_KEY(keyval, ogonek) || \ _IS_DEAD_KEY(keyval, iota) || \ _IS_DEAD_KEY(keyval, voiced_sound) || \ _IS_DEAD_KEY(keyval, semivoiced_sound) || \ _IS_DEAD_KEY(keyval, belowdot) || \ _IS_DEAD_KEY(keyval, hook) || \ _IS_DEAD_KEY(keyval, horn)) #else /* !GDK_dead_hook || !GDK_dead_horn */ #define IS_DEAD_KEY(keyval) \ (_IS_DEAD_KEY(keyval, grave) || \ _IS_DEAD_KEY(keyval, acute) || \ _IS_DEAD_KEY(keyval, circumflex) || \ _IS_DEAD_KEY(keyval, tilde) || \ _IS_DEAD_KEY(keyval, macron) || \ _IS_DEAD_KEY(keyval, breve) || \ _IS_DEAD_KEY(keyval, abovedot) || \ _IS_DEAD_KEY(keyval, diaeresis) || \ _IS_DEAD_KEY(keyval, abovering) || \ _IS_DEAD_KEY(keyval, doubleacute) || \ _IS_DEAD_KEY(keyval, caron) || \ _IS_DEAD_KEY(keyval, cedilla) || \ _IS_DEAD_KEY(keyval, ogonek) || \ _IS_DEAD_KEY(keyval, iota) || \ _IS_DEAD_KEY(keyval, voiced_sound) || \ _IS_DEAD_KEY(keyval, semivoiced_sound) || \ _IS_DEAD_KEY(keyval, belowdot)) #endif /* !GDK_dead_hook || !GDK_dead_horn */ typedef struct _CandidateWindow CandidateWindow; /* Style for gtk input method preedit/status */ struct _GtkIIIMInfo { #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2) GdkScreen *screen; #endif IIIMCF_handle iiim; /* locale is obtained from $IIIM_LOCALE, or LC_CTYPE seeting. */ char *locale; #ifdef DIRTY_HACK_FOR_TEST_PURPOSE /* le is obtained from $IIIM_LE */ char *le_name; #endif GSList *ics; SwitcherInfo *switcher_info; gchar *le_list; /* The following GtkSettings are used to configure appearance of input method status windows. status_style_setting: GTK_IM_STATUS_NOTHING IIIMGCF does not show the status window, but somebody else perhaps GIMLET shows it on their UI. GTK_IM_STATUS_CALLBACK IIIMGCF shows the status window attached to application frame window. GTK_IM_STATUS_NONE no status window anywhere. */ GtkSettings *settings; gulong status_set; GtkIMStatusStyle status_style_setting; }; /* A candiate window */ struct _CandidateWindow { GtkWidget *tree; GtkWidget *toplevel; GtkWidget *frame; /* Toplevel window to which the candiddate window corresponds */ GtkWidget *app_toplevel; GtkListStore *store; gint choices_per_window; gint number_of_rows; gint number_of_columns; gint direction; gulong destroy_handler_id; gulong configure_handler_id; gulong button_press_handler_id; }; /* A context status window; these are kept in the status_windows list. */ struct _StatusWindow { GtkWidget *window; /* Toplevel window to which the status window corresponds */ GtkWidget *toplevel; /* Currently focused GtkIMContextIIIM for the toplevel, if any */ GtkIMContextIIIM *context; }; typedef enum { ON_DESKTOP_PANEL, ATTACH_TO_APP_FRAME } IMStatusPlacement; typedef enum { CONV_OFF = 0, CONV_ON = 1 } ICConversionMode; typedef enum { IM_OFF = 0, IM_ON } IMEnabled; typedef struct { IMEnabled im_enabled; IMEnabled status_enabled; IMEnabled lookup_enabled; IMStatusPlacement status_placement; } IMSettings; static IMSettings current_setting; static gboolean current_setting_enabled; static GdkAtom im_settings_atom = GDK_NONE; static const char *_IM_SETTINGS = "_IM_SETTINGS"; #ifdef HAS_IIIM_PROPERTIES typedef enum { LS_IC, LS_DESKTOP } LangSwitchPolicy; typedef enum { OO_ACTIVATE, OO_DONOT_CHANGE } OnOffPolicy; typedef struct { LangSwitchPolicy lang_switch_policy; OnOffPolicy on_off_policy; int kbd_layout; IMEnabled sync_activation; IMEnabled remember_last_le; } IIIMSettings; static IIIMSettings current_iiim_setting; static Bool current_iiim_setting_enabled; static GdkAtom iiim_settings_atom = GDK_NONE; static GdkAtom desktop_lang_atom = GDK_NONE; static GdkAtom language_list_atom = GDK_NONE; static const char *_IIIM_SETTINGS = "_IIIM_SETTINGS"; static GdkAtom global_conv_mode_atom = GDK_NONE; #endif /* HAS_IIIM_PROPERTIES */ static gboolean on_status_toplevel_configure (GtkWidget *toplevel, GdkEventConfigure *event, StatusWindow *status_window); static void im_context_iiim_class_init (GtkIMContextIIIMClass * class); static void im_context_iiim_init (GtkIMContextIIIM * im_context); static void im_context_iiim_finalize (GObject * obj); static void im_context_iiim_set_client_window (GtkIMContext * context, GdkWindow * client_window); static gboolean im_context_iiim_filter_keypress (GtkIMContext * context, GdkEventKey * key); static void im_context_iiim_reset (GtkIMContext * context); static void im_context_iiim_focus_in (GtkIMContext * context); static void im_context_iiim_focus_out (GtkIMContext * context); static void im_context_iiim_set_cursor_location (GtkIMContext * context, GdkRectangle * area); static void im_context_iiim_set_use_preedit (GtkIMContext * context, gboolean use_preedit); static void im_context_iiim_get_preedit_string (GtkIMContext * context, gchar ** str, PangoAttrList ** attrs, gint * cursor_pos); /* Session Context */ static IIIMCF_context iiim_get_session_context (GtkIMContextIIIM * context_iiim); /* Candidate Window */ static IIIMCF_lookup_choice iiim_get_lookup_choice (GtkIMContextIIIM * context_iiim); static GtkWidget *iiim_get_candidate_window (GtkIMContextIIIM * context_iiim); static void iiim_candidate_show (GtkIMContextIIIM * context_iiim); static void iiim_destroy_candidate_window (GtkIMContextIIIM * context_iiim); static gboolean iiim_candidate_window_configure (GtkWidget * toplevel, GdkEventConfigure * event, GtkIMContextIIIM *context_iiim); static void iiim_candidate_window_button_press (GtkWidget *widget, GdkEventButton *event, GtkIMContextIIIM *context_iiim); /* Key Event */ static void iiim_keylist_free (GtkIMContextIIIM * context_iiim); static gint check_stored_keyevent (GtkIMContext * context, GdkEventKey * event); /* Event */ static gboolean iiim_event_dispatch (GtkIMContextIIIM * context_iiim); static gboolean forward_event (GtkIMContextIIIM * context_iiim, IIIMCF_event ev, IIIMF_status *st_ret); static gchar *utf16_to_utf8 (IIIMCF_text text); static void set_sc_client_window (GtkIMContextIIIM * context_iiim, GdkWindow * client_window, gboolean send_signal); /* Status */ static void update_client_widget (GtkIMContextIIIM *context_iiim); static void update_status_window (GtkIMContextIIIM*context_iiim); static StatusWindow *status_window_get (GtkWidget *toplevel); static void status_window_free (StatusWindow *status_window); static void status_window_set_text (StatusWindow *status_window, const gchar *text); static void reinitialize_sc (GtkIMContextIIIM * context_iiim, gboolean send_signal); static GObjectClass *parent_class; GType gtk_type_im_context_iiim = 0; static GSList *open_iiims = NULL; static gboolean iiim_is_initialized = FALSE; static IIIMCF_handle iiim = NULL; /* List of status windows for different toplevels */ static GSList *status_windows = NULL; void im_context_iiim_register_type (GTypeModule * type_module) { static const GTypeInfo im_context_iiim_info = { sizeof (GtkIMContextIIIMClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) im_context_iiim_class_init, NULL, NULL, sizeof (GtkIMContextIIIM), 0, (GInstanceInitFunc) im_context_iiim_init, }; gtk_type_im_context_iiim = g_type_module_register_type (type_module, GTK_TYPE_IM_CONTEXT, "GtkIMContextIIIM", &im_context_iiim_info, 0); } static void im_context_iiim_class_init (GtkIMContextIIIMClass * 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); im_context_class->set_client_window = im_context_iiim_set_client_window; im_context_class->filter_keypress = im_context_iiim_filter_keypress; im_context_class->reset = im_context_iiim_reset; im_context_class->get_preedit_string = im_context_iiim_get_preedit_string; im_context_class->focus_in = im_context_iiim_focus_in; im_context_class->focus_out = im_context_iiim_focus_out; im_context_class->set_cursor_location = im_context_iiim_set_cursor_location; im_context_class->set_use_preedit = im_context_iiim_set_use_preedit; gobject_class->finalize = im_context_iiim_finalize; return; } /* * Update Status Window Message */ static void status_callback (GtkIMContextIIIM * context_iiim) { gchar *utf8; IIIMCF_context c = iiim_get_session_context (context_iiim); IIIMCF_text text; IIIMF_status st; if (!context_iiim->status_window) { if (!context_iiim->has_focus) { context_iiim->has_focus = TRUE; /* tell switcher that I'm the current client */ im_context_switcher_set_focus (context_iiim); } update_status_window (context_iiim); } if (!context_iiim->status_window) return; st = iiimcf_get_status_text (c, &text); if (st == IIIMF_STATUS_NO_STATUS_TEXT || st != IIIMF_STATUS_SUCCESS) { status_window_set_text (context_iiim->status_window, ""); im_context_switcher_set_status_text (context_iiim, " "); return; } utf8 = utf16_to_utf8 (text); if (current_setting_enabled && (current_setting.im_enabled == IM_OFF || current_setting.status_enabled == IM_OFF)) status_window_set_text (context_iiim->status_window, ""); else if ((current_setting_enabled && current_setting.status_placement == ATTACH_TO_APP_FRAME) || !current_setting_enabled) { status_window_set_text (context_iiim->status_window, utf8); im_context_switcher_set_status_text (context_iiim, utf8); } else { status_window_set_text (context_iiim->status_window, ""); im_context_switcher_set_status_text (context_iiim, utf8); } g_free (utf8); } /* get Lang List */ IIIMCF_language* iiim_get_languages (GtkIIIMInfo *info, int *n_lang) { IIIMF_status st; IIIMCF_handle iiim = info->iiim; IIIMCF_language *lang = NULL; if (!iiim) return NULL; st = iiimcf_get_supported_languages (iiim, n_lang, &lang); if (st != IIIMF_STATUS_SUCCESS) return NULL; return lang; } static char* format_iiimcf_string (const IIIMP_card16 *u16str) { return (u16str != NULL ? g_utf16_to_utf8 ((const gunichar2 *) u16str, -1, NULL, NULL, NULL) : NULL); } static IIIMP_card16 * format_utf8_string (char *utf8str) { return (utf8str != NULL ? g_utf8_to_utf16 (utf8str, -1, NULL, NULL, NULL) : NULL); } static char * get_imelist (int nimeinfos, IIIMCF_imeinfo_rec **ppimeinfos) { int i; char *imelist = NULL, *p; imelist = g_new0 (char, 1024); p = imelist; for (i = 0; i < nimeinfos; ++i) { char *imename = NULL; char ime_seperator = ','; if (!ppimeinfos[i]->enable) continue; if (i) *p++ = ime_seperator; imename = format_iiimcf_string (ppimeinfos[i]->imename); strcpy (p, imename); /* imename shouldn't be NULL */ p += strlen (imename); g_free (imename); } if (*imelist == 0) { g_free (imelist); imelist = NULL; } return imelist; } /* * Get IM List */ static void iiim_get_im_list (GtkIIIMInfo * info) { const IIIMP_card16 *u16idname, *u16hrn, *u16domain; int input_methods_n; gint i; gsize len = 0; IIIMCF_handle handle = info->iiim; IIIMCF_input_method *input_methods; IIIMF_status st; const char *separator = ";"; const char *ime_delimiter = "-"; gsize separator_len; gchar *le_list, *ptr; char *lename = NULL; char *imelist; IIIMCF_imeinfo_rec **ppimeinfos; int nimeinfos; separator_len = strlen (separator); if (!handle) return; st = iiimcf_get_supported_input_methods (handle, &input_methods_n, &input_methods); if (st != IIIMF_STATUS_SUCCESS) return; /* First part, getting length */ for (i = 0; i < input_methods_n; i++) { gint nlangs; gint j; IIIMCF_language *plangs; char *langid; st = iiimcf_get_input_method_desc (input_methods[i], &u16idname, &u16hrn, &u16domain); if (st != IIIMF_STATUS_SUCCESS) goto error; lename = format_iiimcf_string (u16hrn); st = iiimcf_get_input_method_imeinfos (input_methods[i], &nimeinfos, &ppimeinfos); if (st != IIIMF_STATUS_SUCCESS) goto error; /* append the list of IME name to lename */ if (nimeinfos && ppimeinfos && (imelist = get_imelist (nimeinfos, ppimeinfos))) { char *tmp_lename = g_new0 (char, strlen (lename) + strlen (imelist) + 2); sprintf (tmp_lename, "%s%s%s", lename, ime_delimiter, imelist); g_free (lename); lename = tmp_lename; g_free (imelist); } st = iiimcf_get_input_method_languages (input_methods[i], &nlangs, &plangs); if (st != IIIMF_STATUS_SUCCESS) goto error; for (j = 0; j < nlangs; j++) { st = iiimcf_get_language_id (plangs[j], (const char **)&langid); len += (lename != NULL ? strlen (lename) : 0) + 1 + strlen (langid); len += separator_len; } if (lename != NULL) { g_free (lename); lename = NULL; } } /* Second part, building string */ le_list = g_new (gchar, len + 1); ptr = le_list; for (i = 0; i < input_methods_n; i++) { gint nlangs; gint j; IIIMCF_language *plangs; char *langid; st = iiimcf_get_input_method_desc (input_methods[i], &u16idname, &u16hrn, &u16domain); lename = format_iiimcf_string (u16hrn); st = iiimcf_get_input_method_imeinfos (input_methods[i], &nimeinfos, &ppimeinfos); if (st != IIIMF_STATUS_SUCCESS) goto error; /* append the list of IME name to lename */ if (nimeinfos && ppimeinfos && (imelist = get_imelist (nimeinfos, ppimeinfos))) { char *tmp_lename = g_new0 (char, strlen (lename) + strlen (imelist) + 2); sprintf (tmp_lename, "%s%s%s", lename, ime_delimiter, imelist); g_free (lename); lename = tmp_lename; g_free (imelist); } st = iiimcf_get_input_method_languages (input_methods[i], &nlangs, &plangs); if (st != IIIMF_STATUS_SUCCESS) goto error; st = iiimcf_get_language_id (plangs[0], (const char **)&langid); for (j = 0; j < nlangs; j++) { st = iiimcf_get_language_id (plangs[j], (const char **)&langid); ptr = g_stpcpy (ptr, langid); ptr = g_stpcpy (ptr, ":"); ptr = g_stpcpy (ptr, lename); ptr = g_stpcpy (ptr, separator); } if (lename != NULL) { g_free (lename); lename = NULL; } } le_list[len - 1] = 0; info->le_list = le_list; error: if (lename != NULL) g_free (lename); return; } /* * Event Dispatch */ static gboolean iiim_event_dispatch (GtkIMContextIIIM * context_iiim) { IIIMCF_context c; IIIMF_status st; IIIMCF_event ev; IIIMCF_event_type et; gboolean result = TRUE; c = iiim_get_session_context (context_iiim); if (!c) return FALSE; while ((st = iiimcf_get_next_event (c, &ev)) == IIIMF_STATUS_SUCCESS) { st = iiimcf_get_event_type (ev, &et); if (st != IIIMF_STATUS_SUCCESS) continue; DEBUG_DO (g_message ("event type %d", et)); switch (et) { case IIIMCF_EVENT_TYPE_KEYEVENT: { /* We need to send GdkEventKey to a client */ /* GTK+ is in ASYNC mode, key event will be always sent to server by GTK+, * if the key event isn't consumed by server, it is necessary for client framework * to send the key press event and fake one release event to client. * It's required that LE shouldn't try to send back the key release event, * otherwise, duplicate key event would be wrongly created and sent to client. */ GdkEventKey *event; /* GdkEventKey *release_event; */ /* GdkEvent gdk_release_event; */ IIIMCF_keyevent kev; st = iiimcf_get_keyevent_value (ev, &kev); if (st != IIIMF_STATUS_SUCCESS) { result = FALSE; break; } if (0 == (kev.modifier & 0x80000000)) { event = (GdkEventKey *) gdk_event_new (GDK_KEY_PRESS); } else { event = (GdkEventKey *) gdk_event_new (GDK_KEY_RELEASE); kev.modifier &= 0x7fffffff; } st = convert_IIIMCF_keyevent_to_GdkEventKey (&kev, event); if (gdk_keyval_name (event->keyval)) { DEBUG_DO (g_message ("event created %s %d %d %d", gdk_keyval_name (event->keyval), event->hardware_keycode, event->length, event->group)); } if (st != IIIMF_STATUS_SUCCESS) { gdk_event_free ((GdkEvent *) event); result = FALSE; break; } event->window = context_iiim->client_window; /* * increment client_window's ref count. * gdk_event_free decrements it. */ g_object_ref (event->window); gdk_event_put ((GdkEvent *) event); #if 1 /* 2006/02/14 * temporarily revive a section of code to avoid a * problem: ascii chars can not be inputed in Latin * mode with gtk apps */ /* Note for removal: 2006/01/18 * The code appears at 1491 in trunk. */ /* Store GdkEventKey */ context_iiim->keylist = g_slist_append (context_iiim->keylist, event); /* 2006-09-28 * - create fake key release event only when it is key press event. * - store fake event in key list for avoiding to send fake event to server. */ if (event->type == GDK_KEY_PRESS) { /* create Fake Key Release Event */ GdkEventKey *release_event = (GdkEventKey *) gdk_event_new (GDK_KEY_RELEASE); release_event->window = event->window; release_event->state = event->state; release_event->keyval = event->keyval; release_event->hardware_keycode = event->hardware_keycode; /* * increment client_window's ref count. * gdk_event_free decrements it. */ g_object_ref (release_event->window); gdk_event_put ((GdkEvent *) release_event); DEBUG_DO (g_message ("put fake key release event")); /* Store GdkEventKey */ context_iiim->keylist = g_slist_append (context_iiim->keylist, release_event); } #endif /* 0 */ } break; case IIIMCF_EVENT_TYPE_TRIGGER_NOTIFY: im_context_switcher_set_conversion_mode (context_iiim); break; case IIIMCF_EVENT_TYPE_UI_PREEDIT_START: DEBUG_DO (g_message ("preedit start")); if (!context_iiim->finalizing) g_signal_emit_by_name (context_iiim, "preedit_start"); break; case IIIMCF_EVENT_TYPE_UI_PREEDIT_CHANGE: DEBUG_DO (g_message ("preedit changed")); if (!context_iiim->finalizing) g_signal_emit_by_name (context_iiim, "preedit_changed"); break; case IIIMCF_EVENT_TYPE_UI_PREEDIT_DONE: DEBUG_DO (g_message ("preedit end")); /* call preedit_changed here, otherwise preedit text on gtkview and gtkentry remains when conversion is turned off. */ if (!context_iiim->finalizing) { g_signal_emit_by_name (context_iiim, "preedit_changed"); g_signal_emit_by_name (context_iiim, "preedit_end"); } break; case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_START: DEBUG_DO (g_message ("lookup_choice start")); context_iiim->candidate_start = TRUE; iiim_candidate_show (context_iiim); break; case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_CHANGE: iiim_candidate_show (context_iiim); DEBUG_DO (g_message ("lookup_choice change")); break; case IIIMCF_EVENT_TYPE_UI_LOOKUP_CHOICE_DONE: context_iiim->candidate_start = FALSE; iiim_destroy_candidate_window (context_iiim); DEBUG_DO (g_message ("lookup_choice_done")); break; case IIIMCF_EVENT_TYPE_UI_STATUS_START: DEBUG_DO (g_message ("ui_status_start")); status_callback (context_iiim); break; case IIIMCF_EVENT_TYPE_UI_STATUS_CHANGE: DEBUG_DO (g_message ("ui_status_change")); status_callback (context_iiim); break; case IIIMCF_EVENT_TYPE_UI_STATUS_END: DEBUG_DO (g_message ("ui_status_end")); status_callback (context_iiim); break; case IIIMCF_EVENT_TYPE_AUX_START: DEBUG_DO (g_message ("aux_start")); iiim_aux_start (context_iiim, ev); break; case IIIMCF_EVENT_TYPE_AUX_DRAW: DEBUG_DO (g_message ("aux_draw")); iiim_aux_draw (context_iiim, ev); break; case IIIMCF_EVENT_TYPE_AUX_DONE: DEBUG_DO (g_message ("aux_done")); iiim_aux_done (context_iiim, ev); break; case IIIMCF_EVENT_TYPE_AUX_GETVALUES: DEBUG_DO (g_message ("aux_getvalues_reply")); iiim_aux_getvalues_reply (context_iiim, ev); break; case IIIMCF_EVENT_TYPE_UI_COMMIT: { IIIMCF_text text; gchar *utf8 = NULL; st = iiimcf_get_committed_text (c, &text); utf8 = utf16_to_utf8 (text); g_signal_emit_by_name (context_iiim, "commit", utf8); g_free (utf8); } break; default: break; } iiimcf_dispatch_event (c, ev); iiimcf_ignore_event (ev); } return result; } #if (GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >=2) static void iiim_info_display_closed (GdkDisplay * display, gboolean is_error, GtkIIIMInfo * info) { GSList *ics, *tmp_list; open_iiims = g_slist_remove (open_iiims, info); ics = info->ics; info->ics = NULL; for (tmp_list = ics; tmp_list; tmp_list = g_slist_next (tmp_list)) { set_sc_client_window (tmp_list->data, NULL, TRUE); } g_slist_free (tmp_list); g_free (info->locale); #ifdef DIRTY_HACK_FOR_TEST_PURPOSE if (info->le_name) g_free (info->le_name); #endif g_free (info->le_list); /* TODO */ g_free (info); } #endif static void status_style_change (GtkIIIMInfo *info) { GtkIMStatusStyle status_style; g_object_get (info->settings, "gtk-im-status-style", &status_style, NULL); info->status_style_setting = status_style; } static void get_im_settings_property () { GdkWindow *root_window = gdk_get_default_root_window (); GdkAtom type; gulong *data = NULL; gint format; gint length = 0; if (im_settings_atom == GDK_NONE) im_settings_atom = gdk_atom_intern (_IM_SETTINGS, FALSE); if (im_settings_atom == GDK_NONE) return; gdk_property_get (root_window, im_settings_atom, im_settings_atom, 0, INT_MAX, FALSE, &type, &format, &length, (guchar **)&data); if (data) { current_setting.im_enabled = *(data + 0); current_setting.status_enabled = *(data + 1); current_setting.lookup_enabled = *(data + 2); current_setting.status_placement = *(data + 3); g_free (data); current_setting_enabled = TRUE; } else current_setting_enabled = FALSE; } #ifdef HAS_IIIM_PROPERTIES static void get_iiim_settings_property () { GdkWindow *root_window = gdk_get_default_root_window (); GdkAtom type; gulong *data = NULL; gint format; gint length = 0; if (iiim_settings_atom == None) iiim_settings_atom = gdk_atom_intern (_IIIM_SETTINGS, FALSE); if (iiim_settings_atom == GDK_NONE) return; gdk_property_get (root_window, iiim_settings_atom, iiim_settings_atom, 0, INT_MAX, FALSE, &type, &format, &length, (guchar **)&data); if (data) { current_iiim_setting.lang_switch_policy = *(data + 0); current_iiim_setting.on_off_policy = *(data + 1); current_iiim_setting.kbd_layout = *(data + 2); current_iiim_setting.sync_activation = *(data + 3); current_iiim_setting.remember_last_le = *(data + 4); g_free (data); current_iiim_setting_enabled = TRUE; } else current_iiim_setting_enabled = FALSE; } gint get_global_conv_mode () { GdkAtom type; gint format; gint length; guchar *data = NULL; gint mode = -1; if (global_conv_mode_atom == GDK_NONE) global_conv_mode_atom = gdk_atom_intern ("_IIIM_GLOBAL_CONV_MODE", False); if (global_conv_mode_atom == GDK_NONE) return -1; if (gdk_property_get (gdk_get_default_root_window (), global_conv_mode_atom, global_conv_mode_atom, 0, INT_MAX, FALSE, &type, &format, &length, &data)) { if (data != NULL) { if (!strncmp ((gchar *)data, "on", 2)) mode = 1; if (!strncmp ((gchar *)data, "off", 3)) mode = 0; g_free (data); return mode; } } return -1; } void set_global_conv_mode (int mode) { gchar *mode_str; if (global_conv_mode_atom == GDK_NONE) global_conv_mode_atom = gdk_atom_intern ("_IIIM_GLOBAL_CONV_MODE", False); if (global_conv_mode_atom == GDK_NONE) return; mode_str = (mode == 1) ? "on" : "off"; gdk_property_change (gdk_get_default_root_window (), global_conv_mode_atom, global_conv_mode_atom, 8, GDK_PROP_MODE_REPLACE, (unsigned char *)mode_str, strlen (mode_str)); } static gchar * get_desktop_lang () { GdkWindow *root_window = gdk_get_default_root_window (); GdkAtom type; guchar *data = NULL; gint format; gint length; if (desktop_lang_atom == GDK_NONE) desktop_lang_atom = gdk_atom_intern ("_IIIM_SWITCHER_DESKTOP_INPUT_LANGUAGE", True); if (desktop_lang_atom == GDK_NONE) return (gchar *)NULL; if (gdk_property_get (root_window, desktop_lang_atom, desktop_lang_atom, 0, INT_MAX, FALSE, &type, &format, &length, &data)) { if (data != NULL) return (gchar *)data; } return (gchar *)NULL; } static gchar * get_lang_list() { GdkWindow *root_window = gdk_get_default_root_window(); GdkAtom type; guchar *data = NULL; gint format; gint length; if (language_list_atom == GDK_NONE) language_list_atom = gdk_atom_intern ("_IIIM_SWITCHER_INPUT_LANGUAGE_LIST", True); if (language_list_atom == GDK_NONE) return (gchar *)NULL; if (gdk_property_get (root_window, language_list_atom, GDK_TARGET_STRING, 0, INT_MAX, FALSE, &type, &format, &length, &data)) { if (data != NULL) return (gchar *)data; } return (gchar *)NULL; } /************************************************ * singleton objects for super hotkey handling */ static GtkIMContextIIIM *current_context = NULL; static GtkWidget *le_select_window = NULL; static GtkTreeView *le_select_view = NULL; static gchar **le_select_lang_array = NULL; /************************************************/ static gchar **get_lang_array(gint *limit) { gint n; gchar *lang_list = get_lang_list(); gchar **array = g_strsplit_set(lang_list, ";", -1); for (int i = 0;; i++) { if (array[i] == NULL) { *limit = i; break; } } if ((n = (*limit % 3)) != 0) { *limit -= n; } return array; } static gint get_current_id(gchar **lang_array, gint limit) { gchar *cur_lang; gchar *cur_le; gchar *dt_lang = get_desktop_lang(); gint i = 0; gint current_id = 0; if (dt_lang == NULL) { if (current_context != NULL) { dt_lang = g_strdup(current_context->current_language); } else { dt_lang = g_strdup(setlocale (LC_CTYPE, NULL)); } } cur_lang = dt_lang; cur_le = strstr(dt_lang, ":"); if (cur_le != NULL) { dt_lang[cur_le - dt_lang] = '\0'; cur_le++; } for (i = 0; i < limit; i += 3) { if (!strcmp(cur_lang, lang_array[i])) { if (cur_le == NULL || !strcmp(cur_le, lang_array[i + 2])) { current_id = i; break; } } } g_free(dt_lang); return current_id; } static gchar *get_im_string(gboolean next) { gchar *imstr; gint limit; gchar **lang_array = get_lang_array(&limit); gint current_id = get_current_id(lang_array, limit); if (next) { if (current_id + 3 >= limit) { current_id = 0; } else { current_id += 3; } } else { if (current_id < 3) { current_id = limit - 3; } else { current_id -= 3; } } imstr = g_strconcat(lang_array[current_id], ":", lang_array[current_id + 2], NULL); g_strfreev(lang_array); return imstr; } static gchar *get_next_im_string () { return get_im_string(TRUE); } static gchar *get_prev_im_string () { return get_im_string(FALSE); } static void le_select_set_input_language(gchar *lang) { static GdkAtom desktop_lang_atom = GDK_NONE; if (current_context != NULL) { im_context_initialize_with_input_language(current_context, lang); /* check property here */ im_context_switcher_set_conversion_mode (current_context); im_context_switcher_set_input_language (current_context, lang); } if (is_on_at_switch ()) { im_context_change_conversion_mode (current_context, "on"); } if (desktop_lang_atom == GDK_NONE) desktop_lang_atom = gdk_atom_intern ("_IIIM_SWITCHER_DESKTOP_INPUT_LANGUAGE", FALSE); /* if language swiching is per desktop mode, then * desktop language of root window property will be used */ gdk_property_change (gdk_get_default_root_window (), desktop_lang_atom, desktop_lang_atom, 8, GDK_PROP_MODE_REPLACE, (guchar *)lang, strlen (lang) + 1); /* including last NULL */ } static void iiim_candidate_move (GtkIMContextIIIM *, GtkWidget *); static void le_select_window_down () { GtkTreeSelection *selection; GtkTreePath *path; GtkTreeIter iter; GtkTreeModel *model; GtkTreeRowReference *ref; if (!le_select_window || !le_select_view) return; selection = gtk_tree_view_get_selection (le_select_view); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { path = gtk_tree_model_get_path (model, &iter); gtk_tree_path_next (path); ref = gtk_tree_row_reference_new (model, path); if (gtk_tree_row_reference_valid (ref)) { gtk_tree_selection_select_path (selection, path); } else { GtkTreePath *p2 = gtk_tree_path_new_first (); gtk_tree_selection_select_path (selection, p2); gtk_tree_path_free (p2); } gtk_tree_path_free (path); } } static void le_select_window_up () { GtkTreeSelection *selection; GtkTreePath *path; GtkTreeIter iter; GtkTreeModel *model; if (!le_select_window || !le_select_view) return; selection = gtk_tree_view_get_selection (le_select_view); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { path = gtk_tree_model_get_path (model, &iter); if (gtk_tree_path_prev (path)) { gtk_tree_selection_select_path (selection, path); } else { GtkTreeIter *iter2 = NULL; GtkTreePath *path2 = NULL; gboolean valid = TRUE; while (valid) { if (iter2) gtk_tree_iter_free (iter2); iter2 = gtk_tree_iter_copy (&iter); valid = gtk_tree_model_iter_next (model, &iter); } path2 = gtk_tree_model_get_path (model, iter2); gtk_tree_selection_select_path (selection, path2); gtk_tree_path_free (path2); } gtk_tree_path_free (path); } } static void le_select_window_commit () { GtkTreeSelection *selection; GtkTreePath *path; GtkTreeIter iter; GtkTreeModel *model; if (!le_select_window || !le_select_view || !le_select_lang_array) return; selection = gtk_tree_view_get_selection (le_select_view); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gchar *imstr; gint *indices, id; path = gtk_tree_model_get_path (model, &iter); indices = gtk_tree_path_get_indices (path); id = indices[0] * 3; imstr = g_strconcat (le_select_lang_array[id], ":", le_select_lang_array[id + 2], NULL); le_select_set_input_language (imstr); g_free (imstr); } } static void le_select_window_hide () { if (le_select_window && GTK_WIDGET_MAPPED(le_select_window)) { gtk_widget_hide (le_select_window); } } static void le_select_button_press (GtkWidget *widget, GdkEventButton *event, GtkIMContextIIIM *context_iiim) { GtkTreeView *view; GtkTreePath *path; GtkTreeIter iter; GtkTreeModel *model; GtkTreeSelection *selection; gint *indices; view = GTK_TREE_VIEW (widget); selection = gtk_tree_view_get_selection (view); gtk_tree_view_get_path_at_pos (view, event->x, event->y, &path, NULL, NULL, NULL); if (!path || !le_select_lang_array) { /* click outside of candidates */ gtk_widget_hide (le_select_window); return; } gtk_tree_selection_select_path (selection, path); if (event->type == GDK_BUTTON_RELEASE) { gchar *imstr; gint id; indices = gtk_tree_path_get_indices (path); id = indices[0] * 3; imstr = g_strconcat (le_select_lang_array[id], ":", le_select_lang_array[id + 2], NULL); le_select_set_input_language (imstr); gtk_widget_hide (le_select_window); g_free (imstr); } return; } static void le_switch_handler(IIIMCF_context context) { int i, limit; gchar **lang_array = get_lang_array(&limit); GtkWidget *view; GtkListStore *store; GtkTreeIter iter; GtkWidget *top; GtkWidget *frame; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreePath *path; GtkTreeSelection *selection; if (!le_select_window) { top = gtk_window_new (GTK_WINDOW_POPUP); // title string should be retrieved from property // which will be set by iiimx-settings-init using gettext frame = gtk_frame_new ("Language Selection"); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT); view = gtk_tree_view_new (); store = gtk_list_store_new (1, G_TYPE_STRING); g_signal_connect (G_OBJECT (view), "motion-notify-event", G_CALLBACK (le_select_button_press), current_context); g_signal_connect (G_OBJECT (view), "button-press-event", G_CALLBACK (le_select_button_press), current_context); g_signal_connect (G_OBJECT (view), "button-release-event", G_CALLBACK (le_select_button_press), current_context); /* language entry */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("", renderer, "text", 0, NULL); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); gtk_tree_view_set_model (GTK_TREE_VIEW (view), GTK_TREE_MODEL (store)); g_object_unref (store); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); gtk_tree_view_columns_autosize (GTK_TREE_VIEW (view)); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view)), GTK_SELECTION_SINGLE); gtk_container_add (GTK_CONTAINER (top), frame); gtk_container_add (GTK_CONTAINER (frame), view); le_select_view = GTK_TREE_VIEW (view); le_select_window = top; } store = GTK_LIST_STORE (gtk_tree_view_get_model (le_select_view)); gtk_list_store_clear (store); for (i = 0; i < limit; i += 3) { gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, lang_array[i + 1], -1); } if (le_select_lang_array) g_strfreev (le_select_lang_array); le_select_lang_array = lang_array; selection = gtk_tree_view_get_selection (le_select_view); path = gtk_tree_path_new_first (); gtk_tree_selection_select_path (selection, path); gtk_tree_path_free (path); gtk_widget_realize (le_select_window); iiim_candidate_move (current_context, le_select_window); gtk_widget_show_all (le_select_window); } static void le_cycle_handler(IIIMCF_context context) { char *imstr = get_next_im_string(); le_select_set_input_language(imstr); g_free(imstr); } static void le_reverse_cycle_handler(IIIMCF_context context) { char *imstr = get_prev_im_string(); le_select_set_input_language(imstr); g_free(imstr); } #endif /* HAS_IIIM_PROPERTIES */ static void try_to_initialize_iiim (GdkWindow *client_window) { IIIMCF_attr attr; IIIMF_status st; char path[PATH_MAX]; char client_group_id[PATH_MAX]; gchar *g_scr_disp_name = NULL; GdkScreen *screen = gdk_drawable_get_screen (client_window); GdkDisplay *display = gdk_screen_get_display (screen); if (!iiim_is_initialized) { Atom iiimd; Atom client_group; Window iiimx; Display * display_x11; st = iiimcf_initialize (IIIMCF_ATTR_NULL); if (st != IIIMF_STATUS_SUCCESS) goto Error; st = iiimcf_create_attr (&attr); if (st != IIIMF_STATUS_SUCCESS) goto Error; st = iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_CLIENT_TYPE, "Gtk IIIMCF Module"); if (st != IIIMF_STATUS_SUCCESS) goto Error; g_scr_disp_name = gdk_screen_make_display_name(screen); st = iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_CLIENT_X_DISPLAY_NAME, g_scr_disp_name); if (st != IIIMF_STATUS_SUCCESS) goto Error; display_x11 = GDK_DISPLAY_XDISPLAY (display); iiimd = XInternAtom (display_x11, "IIIM_SERVER", True); if (None != iiimd) { iiimx = XGetSelectionOwner (display_x11, iiimd); if (None != iiimx) { Atom type; int format; unsigned long length; unsigned long nitem; unsigned char * data; data = NULL; XGetWindowProperty(display_x11, iiimx, iiimd, 0, INT_MAX, False, XA_STRING, &type, &format, &nitem, &length, &data); if (NULL == data) { } else if (0 == strncmp("uds:", (char *)data, 4)) { strncpy(path, data + 4, sizeof (path)); path[(sizeof (path)) - 1] = '\0'; XFree(data); st = iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_SERVER_ADDRESS, path); if (st != IIIMF_STATUS_SUCCESS) goto Error; st = iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_SERVER_SERVICE, ""); if (st != IIIMF_STATUS_SUCCESS) goto Error; } else { XFree (data); } } } client_group = XInternAtom (display_x11, "IIIM_CLIENT_GROUP", True); if (None != client_group) { iiimx = XGetSelectionOwner (display_x11, client_group); if (None != iiimx) { Atom type; int format; unsigned long length; unsigned long nitem; unsigned char * data; data = NULL; XGetWindowProperty(display_x11, iiimx, client_group, 0, INT_MAX, False, XA_STRING, &type, &format, &nitem, &length, &data); if (NULL != data) { strncpy(client_group_id, data, sizeof (client_group_id)); path[(sizeof (client_group_id)) - 1] = '\0'; XFree(data); st = iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_CLIENT_GROUP, client_group_id); if (st != IIIMF_STATUS_SUCCESS) goto Error; } } } st = iiimcf_create_handle (attr, &iiim); if (st != IIIMF_STATUS_SUCCESS) goto Error; st = iiimcf_destroy_attr (attr); if (iiim) { st = iiim_setup_aux_object (iiim); if (st == IIIMF_STATUS_SUCCESS) iiim_is_initialized = TRUE; } /* set super hotkey handlers */ #ifdef HAS_IIIM_PROPERTIES iiimcf_register_super_hotkey_handler(iiim, le_switch_handler, le_cycle_handler, le_reverse_cycle_handler); #endif /* HAS_IIIM_PROPERTIES */ } Error: if (g_scr_disp_name) g_free(g_scr_disp_name); } #define TIMEOUT_INTERVAL 1000 #define TIMEOUT_LIMIT 5 static guint timeout_id = 0; static guint timeout_times = 0; static gboolean try_to_initialize_iiim_when_timeout (gpointer data) { if (timeout_times >= TIMEOUT_LIMIT) return FALSE; timeout_times ++; try_to_initialize_iiim ((GdkWindow*)data); return (NULL == iiim); } static GtkIIIMInfo * get_iiim (GdkWindow * client_window) { GtkIIIMInfo *info = NULL; #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 GSList *tmp_list; GdkScreen *screen = gdk_drawable_get_screen (client_window); GdkDisplay *display = gdk_screen_get_display (screen); #endif #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 tmp_list = open_iiims; while (tmp_list) { info = tmp_list->data; if (info->screen == screen) return info; tmp_list = tmp_list->next; } #else if (open_iiims) return open_iiims->data; #endif info = NULL; if (!timeout_id) try_to_initialize_iiim (client_window); if (!iiim) return NULL; info = g_new0 (GtkIIIMInfo, 1); open_iiims = g_slist_prepend (open_iiims, info); #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 info->screen = screen; #endif info->iiim = iiim; info->ics = NULL; #ifdef DIRTY_HACK_FOR_TEST_PURPOSE /* Dirty Hack for Test Purpose */ { gchar *locale = NULL; gchar *le_name = NULL; locale = getenv ("IIIM_LOCALE"); le_name = getenv ("IIIM_LE"); if (locale && *locale) info->locale = g_strdup (locale); else info->locale = g_strdup (setlocale (LC_CTYPE, NULL)); if (le_name && *le_name) info->le_name = g_strdup (le_name); } #else info->locale = g_strdup (setlocale (LC_CTYPE, NULL)); #endif info->settings = gtk_settings_get_for_screen (info->screen); if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings), "gtk-im-status-style")) gtk_settings_install_property (g_param_spec_enum ("gtk-im-status-style", _("IM Status Style"), _("Where to show the input method statusbar"), GTK_TYPE_IM_STATUS_STYLE, GTK_IM_STATUS_CALLBACK, G_PARAM_READWRITE)); info->status_set = g_signal_connect_swapped (info->settings, "notify::gtk-im-status-style", G_CALLBACK (status_style_change), info); status_style_change (info); iiim_get_im_list (info); if (!info->iiim) g_warning ("Unable to Connect IIIM input method"); #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 g_signal_connect (display, "closed", G_CALLBACK (iiim_info_display_closed), info); #endif return info; } /* * Candidate Window Handling */ static void iiim_candidate_move (GtkIMContextIIIM *context_iiim, GtkWidget *candwin) { gint x, y; GdkRectangle rect; GtkRequisition requisition; gint width, height; height = gdk_screen_get_height (gtk_widget_get_screen (candwin)); width = gdk_screen_get_width (gtk_widget_get_screen (candwin)); gdk_window_get_frame_extents (candwin->window, &rect); gtk_widget_size_request (candwin, &requisition); gdk_window_get_origin (context_iiim->client_window, &x, &y); x = x + context_iiim->cursor.x; y = y + context_iiim->cursor.y + context_iiim->cursor.height; if (y + requisition.height > height) y = height - requisition.height; else if (y < 0) /* may not happen */ y = 0; if (x + requisition.width > width) x = width - requisition.width; else if (x < 0) x = 0; if (requisition.width > 0 && requisition.height > 0 && (requisition.width < rect.width || requisition.height < rect.height)) gtk_window_resize (GTK_WINDOW (candwin), requisition.width, requisition.height); gtk_window_move (GTK_WINDOW (candwin), x, y); } static void iiim_candidate_show (GtkIMContextIIIM * context_iiim) { GtkWidget *w = iiim_get_candidate_window (context_iiim); IIIMF_status st; IIIMCF_lookup_choice luc; CandidateWindow *cw; GtkTreeIter iter; GtkTreeIter riter; int size; int first_candidate; int last_candidate; int current_candidate; int i, j; IIIMCF_text title, candidate, label; gchar *title_u8 = NULL; int flag; DEBUG_DO (g_message ("candidate show")); if (!w) { DEBUG_DO (g_message ("w not found")); return; } if (!context_iiim->candidate_start) { DEBUG_DO (g_message ("candidate not start")); return; } if (current_setting_enabled && current_setting.lookup_enabled == IM_OFF) return; luc = iiim_get_lookup_choice (context_iiim); if (!luc) { DEBUG_DO (g_message ("fail to obtain IIIMCF_lookup_choice")); return; } title = NULL; st = iiimcf_get_lookup_choice_title (luc, &title); if (st != IIIMF_STATUS_SUCCESS) { DEBUG_DO (g_message ("Failed to get lookup choice title")); return; } if (title) title_u8 = utf16_to_utf8 (title); cw = g_object_get_data (G_OBJECT (w), "iiim-candidate-win"); if (!cw) { DEBUG_DO (g_message ("candidate window not found")); return; } gtk_frame_set_label(GTK_FRAME (cw->frame), title_u8); if (title_u8) g_free (title_u8); /* get candidates's amount from IIIMSF */ size = 0; first_candidate = 0; last_candidate = 0; current_candidate = 0; st = iiimcf_get_lookup_choice_size (luc, &size, &first_candidate, &last_candidate, ¤t_candidate); if (st != IIIMF_STATUS_SUCCESS) return; DEBUG_DO (g_message ("size %d first %d last %d current %d", size, first_candidate, last_candidate, current_candidate)); /* some LE sends negative value :-< */ if (current_candidate < 0) current_candidate = 0; /* clear */ gtk_list_store_clear (cw->store); /* set iter */ gtk_list_store_append (cw->store, &iter); /* adding candidate to treeview */ for (i = first_candidate, j = 0; i < (last_candidate + 1); i++, j++) { gchar *candidate_u8, *label_u8 = NULL, *result = NULL; /* get candidates from IIIMSF */ candidate = NULL; label = NULL; flag = 0; st = iiimcf_get_lookup_choice_item (luc, i, &candidate, &label, &flag); if (st != IIIMF_STATUS_SUCCESS) break; if (label) label_u8 = utf16_to_utf8 (label); candidate_u8 = utf16_to_utf8 (candidate); if (candidate_u8) { DEBUG_DO (g_message ("candidate %s", candidate_u8)); } if (label_u8) { result = g_strconcat (label_u8, " ", candidate_u8, NULL); } /* max columns */ if (j == cw->number_of_columns) { /* set next row */ gtk_list_store_insert_after (cw->store, &riter, &iter); iter = riter; j = 0; } gtk_list_store_set (cw->store, &iter, j, result ? result : candidate_u8, -1); /* current candidate */ if (i == current_candidate) { GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (cw->tree)); gtk_tree_selection_select_iter (selection, &iter); } if (result) { g_free (result); g_free (label_u8); } g_free (candidate_u8); } gtk_widget_realize (w); iiim_candidate_move (context_iiim, w); gtk_widget_show_all (w); } static GtkListStore * iiim_create_candidate_model (int number_of_columns) { GtkListStore *ret; GType *types; int i; DEBUG_DO (g_message ("create_candidate_model")); types = g_new0 (GType, number_of_columns); for (i = 0; i < number_of_columns; i++) { types[i] = G_TYPE_STRING; } ret = gtk_list_store_newv (number_of_columns, types); g_free (types); return ret; } static void iiim_candidate_window_button_press (GtkWidget *widget, GdkEventButton *event, GtkIMContextIIIM *context_iiim) { GtkTreeView *tree_view; GtkTreePath *path; GtkTreeIter iter; GtkTreeModel *model; GtkTreeSelection *selection; tree_view = GTK_TREE_VIEW (widget); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL); if (!path) /* click event occured on no candidate */ return; gtk_tree_selection_select_path (selection, path); if (gtk_tree_selection_get_selected (selection, &model, &iter)) { GdkEventKey *e; IIIMF_status st; IIIMCF_keyevent kev; IIIMCF_event ev; gchar *buffer = NULL; gtk_tree_model_get (model, &iter, 0, &buffer, -1); /* assemble the GdkEventKey */ e = (GdkEventKey *) gdk_event_new (GDK_KEY_PRESS); e->time = gdk_event_get_time ((GdkEvent *)event); gdk_event_get_state ((GdkEvent *)event, &e->state); /* buffer[0] always holds a candidate index character. */ e->keyval = buffer[0]; g_free (buffer); st = convert_GdkEventKey_to_IIIMCF_keyevent (e, &kev); gdk_event_free ((GdkEvent *)e); if (st != IIIMF_STATUS_SUCCESS) return; st = iiimcf_create_keyevent (&kev, &ev); if (st != IIIMF_STATUS_SUCCESS) return; /* Send Message to IIIMSF */ if (forward_event (context_iiim, ev, &st)) iiim_event_dispatch (context_iiim); } return; } static GtkWidget * iiim_create_candidate_window (GtkIMContextIIIM * context_iiim) { CandidateWindow *w; GtkListStore *store; GtkCellRenderer *renderer; GtkTreeViewColumn *column; IIIMCF_lookup_choice luc = iiim_get_lookup_choice (context_iiim); IIIMF_status st; IIIMCF_text title; GdkWindow *toplevel_gdk; GtkWidget *toplevel; gpointer ptoplevel; gchar *title_u8 = NULL; int choices_per_window; int number_of_rows; int number_of_columns; int direction; int i; #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 GdkScreen *screen; GdkWindow *root_window; #endif if (!context_iiim->client_window) return NULL; toplevel_gdk = context_iiim->client_window; #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 screen = gdk_drawable_get_screen (toplevel_gdk); root_window = gdk_screen_get_root_window (screen); #endif while (TRUE) { GdkWindow *parent = gdk_window_get_parent (toplevel_gdk); #if GTK_MAJOR_VERSION == 2 && GTK_MINOR_VERSION >= 2 if (parent == root_window) #else if (parent == gdk_get_default_root_window ()) #endif break; else toplevel_gdk = parent; } gdk_window_get_user_data (toplevel_gdk, &ptoplevel); toplevel = GTK_WIDGET(ptoplevel); if (!toplevel) return NULL; DEBUG_DO (g_message ("create candidate window")); if (!luc) { DEBUG_DO (g_message ("lookup choice not found")); return NULL; } choices_per_window = 0; number_of_rows = 0; number_of_columns = 0; direction = IIIMCF_LOOKUP_CHOICE_HORIZONTAL_DIRECTION; /* default */ st = iiimcf_get_lookup_choice_configuration (luc, &choices_per_window, &number_of_rows, &number_of_columns, &direction); if (st != IIIMF_STATUS_SUCCESS) { DEBUG_DO (g_message ("config failed")); return NULL; } title = NULL; st = iiimcf_get_lookup_choice_title (luc, &title); if (st != IIIMF_STATUS_SUCCESS) { DEBUG_DO (g_message ("Failed to get lookup choice title")); return NULL; } if (title) title_u8 = utf16_to_utf8 (title); if ((number_of_columns < 0) || (number_of_rows < 0)) { DEBUG_DO (g_message ("column %d %d", number_of_columns, number_of_rows)); return NULL; } store = iiim_create_candidate_model (number_of_columns); if (!store) { DEBUG_DO (g_message ("create model failed")); return NULL; } w = g_new0 (CandidateWindow, 1); w->toplevel = gtk_window_new (GTK_WINDOW_POPUP); gtk_container_set_border_width (GTK_CONTAINER (w->toplevel), 2); w->frame = gtk_frame_new (title_u8); gtk_frame_set_shadow_type (GTK_FRAME (w->frame), GTK_SHADOW_ETCHED_OUT); w->tree = gtk_tree_view_new (); gtk_tree_view_set_model (GTK_TREE_VIEW (w->tree), GTK_TREE_MODEL (store)); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (w->tree), FALSE); gtk_tree_view_columns_autosize (GTK_TREE_VIEW (w->tree)); for (i = 0; i < number_of_columns; i++) { renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("", renderer, "text", i, NULL); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (GTK_TREE_VIEW (w->tree), column); } gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (w->tree)), GTK_SELECTION_SINGLE); w->store = store; w->app_toplevel = toplevel; w->choices_per_window = choices_per_window; w->number_of_rows = number_of_rows; w->number_of_columns = number_of_columns; w->direction = direction; w->button_press_handler_id = g_signal_connect (G_OBJECT (w->tree), "button-press-event", G_CALLBACK (iiim_candidate_window_button_press), context_iiim); gtk_container_add (GTK_CONTAINER (w->toplevel), w->frame); gtk_container_add (GTK_CONTAINER (w->frame), w->tree); g_object_set_data (G_OBJECT (w->toplevel), "iiim-candidate-win", (gpointer) w); DEBUG_DO (g_message ("create_candidate_window")); if (title_u8) g_free (title_u8); w->configure_handler_id = g_signal_connect (toplevel, "configure_event", G_CALLBACK (iiim_candidate_window_configure), context_iiim); w->destroy_handler_id = g_signal_connect_swapped (toplevel, "destroy", G_CALLBACK (iiim_destroy_candidate_window), context_iiim); return w->toplevel; } static void iiim_destroy_candidate_window (GtkIMContextIIIM * context_iiim) { GtkWidget *w = context_iiim->candidate; CandidateWindow *cw; if (!w) return; cw = gtk_object_get_data (GTK_OBJECT (w), "iiim-candidate-win"); g_signal_handler_disconnect (cw->app_toplevel, cw->destroy_handler_id); g_signal_handler_disconnect (cw->app_toplevel, cw->configure_handler_id); g_signal_handler_disconnect (cw->tree, cw->button_press_handler_id); gtk_widget_destroy (cw->toplevel); g_free (cw); context_iiim->candidate = NULL; return; } static gboolean iiim_candidate_window_configure (GtkWidget * toplevel, GdkEventConfigure * event, GtkIMContextIIIM * context_iiim) { GtkWidget *candwin = iiim_get_candidate_window (context_iiim); if (!candwin) return FALSE; iiim_candidate_move (context_iiim, candwin); return FALSE; } static GtkWidget * iiim_get_candidate_window (GtkIMContextIIIM * context_iiim) { GtkWidget *candidate = NULL; if (!context_iiim->candidate_start) { DEBUG_DO (g_message ("candidate not started yet")); return NULL; } if (context_iiim->candidate) { return context_iiim->candidate; } else { candidate = iiim_create_candidate_window (context_iiim); DEBUG_DO (g_message ("candidate %p", candidate)); context_iiim->candidate = candidate; } return candidate; } static IIIMCF_lookup_choice iiim_get_lookup_choice (GtkIMContextIIIM * context_iiim) { IIIMCF_context context = iiim_get_session_context (context_iiim); IIIMCF_lookup_choice luc; IIIMF_status st; if (context && context_iiim->candidate_start) { if (context_iiim->lookup_choice) return context_iiim->lookup_choice; luc = NULL; st = iiimcf_get_lookup_choice (context, &luc); if (st != IIIMF_STATUS_SUCCESS) return NULL; context_iiim->lookup_choice = luc; return context_iiim->lookup_choice; } return NULL; } static IIIMCF_language get_input_language (GtkIMContextIIIM *context_iiim, gchar *input_language, gboolean exact_match) { GtkIIIMInfo *info = context_iiim->iiim_info; IIIMF_status st; IIIMCF_language cflang; const char *langid; cflang = iiimcf_get_input_language_for_locale (info->iiim, input_language, TRUE); st = iiimcf_get_language_id (cflang, &langid); if (IIIMF_STATUS_SUCCESS == st) { g_free(context_iiim->current_language); context_iiim->current_language = g_strdup(langid); } return cflang; } #ifdef DEBUG static void next_input_language (GtkIMContextIIIM *context_iiim) { int i; GtkIIIMInfo *info = context_iiim->iiim_info; IIIMF_status st; char *langid; int n_lang; IIIMCF_language *lang_list; lang_list = iiim_get_languages (info, &n_lang); if (lang_list) { for (i = 0; i < n_lang; i++) { st = iiimcf_get_language_id (lang_list[i], (const char **)&langid); if (st != IIIMF_STATUS_SUCCESS) continue; if (!strncmp (langid, context_iiim->current_language, strlen (langid))) break; } if (i == n_lang - 1) i = 0; else i++; st = iiimcf_get_language_id (lang_list[i], (const char **)&langid); if (st != IIIMF_STATUS_SUCCESS) return; g_free (context_iiim->current_language); context_iiim->current_language = g_strdup (langid); } return; } static void prev_input_language (GtkIMContextIIIM *context_iiim) { int i; GtkIIIMInfo *info = context_iiim->iiim_info; IIIMF_status st; char *langid; int n_lang; IIIMCF_language *lang_list; lang_list = iiim_get_languages (info, &n_lang); if (lang_list) { for (i = n_lang - 1; i > 0; i--) { st = iiimcf_get_language_id (lang_list[i], (const char **)&langid); if (st != IIIMF_STATUS_SUCCESS) continue; if (!strncmp (langid, context_iiim->current_language, strlen (langid))) break; } if (i == 0) i = n_lang - 1; else i--; st = iiimcf_get_language_id (lang_list[i], (const char **)&langid); if (st != IIIMF_STATUS_SUCCESS) return; g_free (context_iiim->current_language); context_iiim->current_language = g_strdup (langid); } return; } #endif #ifdef HAS_IIIM_PROPERTIES static gchar *get_default_engine (GtkIMContextIIIM *context_iiim) { gchar *lang_id; Atom lang_list_atom; Display *display; Screen screen; XTextProperty text_props; gchar *engine_id = NULL; if (context_iiim == NULL) return NULL; if ((lang_id = context_iiim->current_language) == NULL) return NULL; display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); lang_list_atom = XInternAtom (display, "_IIIM_SWITCHER_INPUT_LANGUAGE_LIST", True); if (lang_list_atom == None) return NULL; text_props.value = NULL; XGetTextProperty (display, RootWindow (display, DefaultScreen (display)), &text_props, lang_list_atom); if (text_props.value != NULL) { gint i; gchar **list; list = g_strsplit ((gchar *)text_props.value, ";", -1); for (i = 0; list[i] != NULL && list[i + 1] != NULL && list[i + 2] != NULL ; i += 3) { if (!strcmp (list[i], lang_id)) { if (strlen (list[i + 2]) != 0) { engine_id = strdup (list[i + 2]); } break; } } g_strfreev (list); g_free(text_props.value); } return engine_id; } #endif /* HAS_IIIM_PROPERTIES */ static Bool vkb_commit_filter(Display *display, Window window, XEvent *eventp, XPointer context) { IIIMF_status st; Atom vkb_target_atom = XInternAtom (display, "_IIIM_VKB_TARGET", False); if (eventp->type != ClientMessage || eventp->xclient.message_type != vkb_target_atom) { /* This is not event from VKB */ return False; } GtkIMContextIIIM *iiim_context = (GtkIMContextIIIM *)context; IIIMCF_context ctx = iiim_get_session_context (iiim_context); /* get stored commit string here */ iiimcf_forward_event_with_operations (ctx, NULL, IIIMCF_GET_STORED_CONTENTS); iiim_event_dispatch (iiim_context); return True; } static IIIMCF_context iiim_get_session_context (GtkIMContextIIIM * context_iiim) { IIIMF_status st; IIIMCF_attr attr; get_iiim_settings_property (); if (!context_iiim->iiim_info) { if (context_iiim->client_window) context_iiim->iiim_info = get_iiim (context_iiim->client_window); if (context_iiim->iiim_info) context_iiim->iiim_info->ics = g_slist_prepend (context_iiim->iiim_info->ics, context_iiim); } if (!context_iiim->iiim_info) { DEBUG_DO (g_message ("iiim_info->iiim not found")); return NULL; } if (!context_iiim->context && context_iiim->iiim_info) { IIIMCF_language iiim_lang = NULL; gchar *language = NULL; #ifdef HAS_IIIM_PROPERTIES gchar *default_engine = NULL; gchar **elms = NULL; gchar *desktop_lang = get_desktop_lang (); if (is_remember_last_le()) { if (desktop_lang != NULL) { elms = g_strsplit (desktop_lang, ":", -1); language = elms[0]; } } #endif /* HAS_IIIM_PROPERTIES */ iiim_lang = get_input_language (context_iiim, language?language:context_iiim->iiim_info->locale, FALSE); #ifdef HAS_IIIM_PROPERTIES g_strfreev (elms); g_free (desktop_lang); #endif /* HAS_IIIM_PROPERTIES */ st = iiimcf_create_attr (&attr); if (st != IIIMF_STATUS_SUCCESS) return NULL; if (iiim_lang) iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_LANGUAGE, iiim_lang); #ifdef HAS_IIIM_PROPERTIES context_iiim->kbd_layout = current_iiim_setting.kbd_layout; st = iiimcf_attr_put_integer_value(attr, IIIMCF_ATTR_KBD_LAYOUT, context_iiim->kbd_layout); if (st != IIIMF_STATUS_SUCCESS) return NULL; /* default LE handling */ if (iiim_lang) { default_engine = get_default_engine (context_iiim); if (default_engine != NULL) { iiimcf_attr_put_string_value (attr, IIIMCF_ATTR_INPUT_METHOD_NAME, default_engine); } } #endif /* HAS_IIIM_PROPERTIES */ st = iiimcf_create_context (context_iiim->iiim_info->iiim, attr, &(context_iiim->context)); #ifdef HAS_IIIM_PROPERTIES g_free(default_engine); #endif /* HAS_IIIM_PROPERTIES */ iiimcf_destroy_attr (attr); if (st != IIIMF_STATUS_SUCCESS) return NULL; _XRegisterFilterByType(GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()), GDK_WINDOW_XID(context_iiim->client_window), ClientMessage, ClientMessage, vkb_commit_filter, (XPointer)context_iiim); } DEBUG_DO (g_message ("%p", context_iiim->context)); return context_iiim->context; } static void im_context_iiim_commit_cb(GtkIMContext *context, const gchar *string, GtkIMContextIIIM *context_iiim) { g_return_if_fail(string != NULL); g_signal_emit_by_name(context_iiim, "commit", string); } static void im_context_iiim_init (GtkIMContextIIIM * im_context_iiim) { im_context_iiim->context = NULL; im_context_iiim->candidate = NULL; im_context_iiim->keylist = NULL; im_context_iiim->candidate_start = FALSE; im_context_iiim->use_preedit = FALSE; im_context_iiim->finalizing = FALSE; im_context_iiim->has_focus = FALSE; im_context_iiim->in_toplevel = FALSE; /* for the dead key */ im_context_iiim->slave = g_object_new(GTK_TYPE_IM_CONTEXT_SIMPLE, NULL); g_signal_connect(G_OBJECT(im_context_iiim->slave), "commit", G_CALLBACK(im_context_iiim_commit_cb), im_context_iiim); memset(&im_context_iiim->saved_key, 0, sizeof(im_context_iiim->saved_key)); bindtextdomain (GETTEXT_PACKAGE, IIIMGCFLOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); } static gchar * utf16_to_utf8 (IIIMCF_text text) { IIIMF_status st; IIIMP_card16 *u16str = NULL; st = iiimcf_get_text_utf16string (text, (const IIIMP_card16 **) &u16str); return (u16str != NULL ? g_utf16_to_utf8 ((const gunichar2 *) u16str, -1, NULL, NULL, NULL) : NULL); } static void iiim_keylist_free (GtkIMContextIIIM * context_iiim) { while (context_iiim->keylist != NULL) { GdkEventKey *ev = (GdkEventKey *)context_iiim->keylist->data; context_iiim->keylist = g_slist_remove (context_iiim->keylist, ev); gdk_event_free ((GdkEvent *) ev); } } /* 2006-09-28 * - define KEY_EVENT_PROCESS_STATUS, and use it as return value. * - append event type check. * - change to remove previous events when removing the event from key list. */ typedef enum { KEY_EVENT_UNPROCESSED = -1, KEY_EVENT_PROCESSED = 0, KEY_EVENT_COMMITED = 1, } KEY_EVENT_PROCESS_STATUS; static KEY_EVENT_PROCESS_STATUS check_stored_keyevent (GtkIMContext * context, GdkEventKey * event) { KEY_EVENT_PROCESS_STATUS result = KEY_EVENT_UNPROCESSED; GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); GSList *tmp; GdkEventKey *saved_event = &(context_iiim->saved_key); /* More detail see bug 6226542: gedit hangs with Click + ESC/CapsLock/NumLock/ScrollLock/F2/F8/F12. gdk_grad_add() will cause two same key_event be sent for grabbed window and focus window. so need first check this event. */ if (event->type == saved_event->type && event->time == saved_event->time && event->keyval == saved_event->keyval) { result = KEY_EVENT_PROCESSED; } context_iiim->saved_key = *event; for (tmp = context_iiim->keylist; tmp; tmp = g_slist_next (tmp)) { GdkEventKey *ev = (GdkEventKey *) tmp->data; if (ev->type == event->type && ev->time == event->time && ev->keyval == event->keyval) { /* found */ guint32 unicode; result = KEY_EVENT_PROCESSED; /* remove previous events */ while (context_iiim->keylist && context_iiim->keylist != tmp) { gdk_event_free ((GdkEvent *) context_iiim->keylist->data); context_iiim->keylist = g_slist_delete_link (context_iiim->keylist, context_iiim->keylist); } /* remove the event */ context_iiim->keylist = g_slist_delete_link (context_iiim->keylist, tmp); unicode = gdk_keyval_to_unicode (event->keyval); if (gdk_keyval_name (ev->keyval)) { DEBUG_DO (g_message ("keyevent found %s %d %d", gdk_keyval_name (ev->keyval), g_unichar_isprint (unicode), unicode)); } if (event->type == GDK_KEY_PRESS && g_unichar_isprint (unicode) && (event->state & ~GDK_SHIFT_MASK) == 0) { gchar utf8[7]; gint len; len = g_unichar_to_utf8 (unicode, utf8); utf8[len] = 0; g_signal_emit_by_name (context, "commit", utf8); result = KEY_EVENT_COMMITED; } gdk_event_free ((GdkEvent *) ev); break; } } return result; } static void set_error_message (GtkIMContextIIIM * context_iiim) { #ifdef DEBUG /* set error messages to status window */ char *error_message = _("Can't communicate with IIIM server"); if (!context_iiim->status_window) { if (!context_iiim->has_focus) { context_iiim->has_focus = TRUE; /* tell switcher that I'm the current client */ im_context_switcher_set_focus (context_iiim); context_iiim->has_focus = FALSE; } update_status_window (context_iiim); } status_window_set_text (context_iiim->status_window, error_message); im_context_switcher_set_status_text (context_iiim, error_message); #endif } static gboolean forward_event (GtkIMContextIIIM * context_iiim, IIIMCF_event ev, IIIMF_status *st_ret) { IIIMCF_context c; IIIMF_status st; c = iiim_get_session_context (context_iiim); if (!c) { set_error_message (context_iiim); if (st_ret) *st_ret = IIIMF_STATUS_EVENT_NOT_FORWARDED; return FALSE; } /* Send Message to IIIMSF */ st = iiimcf_forward_event (c, ev); if (st_ret) *st_ret = st; DEBUG_DO (g_message ("iiimcf_forward_event %d", st)); switch (st) { case IIIMF_STATUS_SUCCESS: break; case IIIMF_STATUS_IC_INVALID: case IIIMF_STATUS_EVENT_NOT_FORWARDED: break; case IIIMF_STATUS_STREAM_SEND: case IIIMF_STATUS_STREAM_RECEIVE: case IIIMF_STATUS_CONNECTION_CLOSED: set_error_message (context_iiim); break; default: status_window_set_text (context_iiim->status_window, ""); break; } return (st == IIIMF_STATUS_SUCCESS); } static gboolean im_context_iiim_filter_keypress (GtkIMContext * context, GdkEventKey * event) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); IIIMF_status st; IIIMCF_keyevent kev; IIIMCF_event ev; static gboolean was_dead_key = FALSE; int icode; int mod_kind; if (!context_iiim->iiim_info) { if (!timeout_id) timeout_id = g_timeout_add (TIMEOUT_INTERVAL, try_to_initialize_iiim_when_timeout, context_iiim->client_window); return gtk_im_context_filter_keypress(context_iiim->slave, event); } /* IIIMF doesn't recognize */ if ((GDK_KEY_RELEASE == event->type) && (NULL != context_iiim->iiim_info) && (IIIMF_STATUS_SUCCESS != iiimcf_is_capability_supported(context_iiim->iiim_info->iiim, IIIMP_CAPABILITY_KEY_RELEASE))) { return FALSE; } if (current_setting_enabled && current_setting.im_enabled == IM_OFF) goto commit_this_event; if (gdk_keyval_name (event->keyval)) { DEBUG_DO (g_message ("event in %s %d %d %d", gdk_keyval_name (event->keyval), event->hardware_keycode, event->length, event->group)); } /* 2006-09-28 * use defined constant. */ switch (check_stored_keyevent (context, event)) { case KEY_EVENT_PROCESSED: return FALSE; case KEY_EVENT_COMMITED: return TRUE; case KEY_EVENT_UNPROCESSED: default: break; } /* * super hotkey (LE SWITCH) handling */ if (event->type == GDK_KEY_PRESS && le_select_window && GTK_WIDGET_MAPPED(le_select_window)) { /* language selection popup in active */ switch (event->keyval) { case GDK_space: case GDK_downarrow: case GDK_Down: le_select_window_down (); break; case GDK_uparrow: case GDK_Up: le_select_window_up (); break; case GDK_Return: le_select_window_commit (); default: le_select_window_hide (); } return TRUE; } #ifdef DEBUG if (event->state == GDK_CONTROL_MASK) { if (event->keyval == GDK_Down) { next_input_language (context_iiim); im_context_initialize_with_input_language (context_iiim, NULL); im_context_switcher_set_input_language (context_iiim, NULL); return FALSE; } else if (event->keyval == GDK_Up) { prev_input_language (context_iiim); im_context_initialize_with_input_language (context_iiim, NULL); im_context_switcher_set_input_language (context_iiim, NULL); return FALSE; } } #endif st = convert_GdkEventKey_to_IIIMCF_keyevent (event, &kev); if (st != IIIMF_STATUS_SUCCESS) goto commit_this_event; /* L&R modifier support for client side hotkey handling */ mod_kind = IIIMCF_NO_MODIFIER_STATE; switch(event->keyval) { case GDK_Shift_L: mod_kind = IIIMCF_SHIFT_L_STATE; break; case GDK_Shift_R: mod_kind = IIIMCF_SHIFT_R_STATE; break; case GDK_Control_L: mod_kind = IIIMCF_CONTROL_L_STATE; break; case GDK_Control_R: mod_kind = IIIMCF_CONTROL_R_STATE; break; case GDK_Meta_L: mod_kind = IIIMCF_META_L_STATE; break; case GDK_Meta_R: mod_kind = IIIMCF_META_R_STATE; break; case GDK_Alt_L: mod_kind = IIIMCF_ALT_L_STATE; break; case GDK_Alt_R: mod_kind = IIIMCF_ALT_R_STATE; break; case GDK_ISO_Level3_Shift: mod_kind = IIIMCF_ALT_G_STATE; break; } if (mod_kind != IIIMCF_NO_MODIFIER_STATE) { IIIMCF_context ctx = iiim_get_session_context(GTK_IM_CONTEXT_IIIM(context)); if (ctx == NULL) goto commit_this_event; if (event->type == GDK_KEY_PRESS) { iiimcf_set_modifier_state(ctx, mod_kind); } else if (event->type == GDK_KEY_RELEASE) { iiimcf_unset_modifier_state(ctx, mod_kind); } } st = iiimcf_create_keyevent (&kev, &ev); if (st != IIIMF_STATUS_SUCCESS) goto commit_this_event; /* Send Message to IIIMSF */ if (forward_event (context_iiim, ev, &st)) return iiim_event_dispatch (context_iiim); if (st != IIIMF_STATUS_EVENT_NOT_FORWARDED && st != IIIMF_STATUS_IC_INVALID) return FALSE; #if defined (sun) /* normalize sun dead key */ switch (event->keyval) { case SunXK_FA_Grave: event->keyval = GDK_dead_grave; break; case SunXK_FA_Circum: event->keyval = GDK_dead_circumflex; break; case SunXK_FA_Acute: event->keyval = GDK_dead_acute; break; case SunXK_FA_Tilde: event->keyval = GDK_dead_tilde; break; case SunXK_FA_Diaeresis: event->keyval = GDK_dead_diaeresis; break; case SunXK_FA_Cedilla: event->keyval = GDK_dead_cedilla; } #endif commit_this_event: if (IS_DEAD_KEY(event->keyval)) { /* don't send the dead key with the commit event! */ was_dead_key = TRUE; return gtk_im_context_filter_keypress(context_iiim->slave, event); } else if (was_dead_key == TRUE) { /* * I'm not sure why below 2 lines are here, but this cases strange problem * with Solaris/Nevada GTK application. The problem is * 2nd level dead key (like dead_deaeresis on French keyboard) does not work * if deadkey is released before Shift key. * So comment out these for now. (11/20/2007 - naoyuki) * if (event->type == GDK_KEY_RELEASE) was_dead_key = FALSE; */ return gtk_im_context_filter_keypress(context_iiim->slave, event); } was_dead_key = FALSE; if (event->state & (gtk_accelerator_get_default_mod_mask () & ~GDK_SHIFT_MASK)) return FALSE; else { /* commit this event */ guint32 unicode; /* don't need to commit this event twice */ if (event->type == GDK_KEY_RELEASE) return FALSE; unicode = gdk_keyval_to_unicode (event->keyval); if (g_unichar_isprint (unicode)) { gchar utf8[7]; gint len; len = g_unichar_to_utf8 (unicode, utf8); utf8[len] = 0; g_signal_emit_by_name (context, "commit", utf8); return TRUE; } } return FALSE; } static void set_sc_client_window (GtkIMContextIIIM * context_iiim, GdkWindow * client_window, gboolean send_signal) { DEBUG_DO (g_message ("set_sc_client_window")); reinitialize_sc (context_iiim, TRUE); if (context_iiim->client_window) { DEBUG_DO (g_message ("set_sc_client_window 1")); if (context_iiim->iiim_info) context_iiim->iiim_info->ics = g_slist_remove (context_iiim->iiim_info->ics, context_iiim); context_iiim->iiim_info = NULL; } context_iiim->client_window = client_window; if (context_iiim->client_window) { DEBUG_DO (g_message ("set_sc_client_window 2")); context_iiim->iiim_info = get_iiim (context_iiim->client_window); if (context_iiim->iiim_info) context_iiim->iiim_info->ics = g_slist_prepend (context_iiim->iiim_info->ics, context_iiim); } update_client_widget (context_iiim); } static void im_context_iiim_set_client_window (GtkIMContext * context, GdkWindow * client_window) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); DEBUG_DO (g_message ("iiim_set_client_window")); set_sc_client_window (context_iiim, client_window, TRUE); DEBUG_DO (g_message ("set client window")); } static void im_context_iiim_finalize (GObject * obj) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (obj); context_iiim->finalizing = TRUE; IIim_aux_destrory_ic (context_iiim); set_sc_client_window (context_iiim, NULL, FALSE); if (context_iiim->candidate) iiim_destroy_candidate_window (context_iiim); im_context_switcher_finalize (context_iiim); g_free (context_iiim->current_language); iiim_keylist_free (context_iiim); g_signal_handlers_disconnect_by_func(context_iiim->slave, (gpointer)im_context_iiim_commit_cb, context_iiim); DEBUG_DO (g_message ("im_context_iiim_finalize")); G_OBJECT_CLASS(parent_class)->finalize(obj); } static void reinitialize_sc (GtkIMContextIIIM * context_iiim, gboolean send_signal) { IIIMF_status st; if (context_iiim->context) { st = iiimcf_destroy_context (context_iiim->context); context_iiim->context = NULL; update_status_window (context_iiim); context_iiim->lookup_choice = NULL; g_free (context_iiim->current_language); context_iiim->current_language = NULL; #ifdef HAS_IIIM_PROPERTIES g_free (context_iiim->current_le); context_iiim->current_le = NULL; #endif /* HAS_IIIM_PROPERTIES */ } } #ifdef HAS_IIIM_PROPERTIES gboolean is_sync_activation () { if (current_iiim_setting_enabled && current_iiim_setting.sync_activation == IM_ON) return TRUE; return FALSE; } gboolean is_on_at_switch () { if (current_iiim_setting_enabled && current_iiim_setting.on_off_policy == OO_ACTIVATE) return TRUE; return FALSE; } gboolean is_remember_last_le() { if (current_iiim_setting_enabled && current_iiim_setting.remember_last_le == IM_ON) return TRUE; return FALSE; } #endif /* HAS_IIIM_PROPERTIES */ static void im_context_iiim_focus_in (GtkIMContext * context) { IIIMF_status st; IIIMCF_event ev; IIIMCF_context c; GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); GtkWidget *cand_win = iiim_get_candidate_window (context_iiim); guint has_focus; DEBUG_DO (g_message ("im_context_iiim_focus_in")); get_im_settings_property (); has_focus = context_iiim->has_focus; current_context = context; if (!has_focus) { c = iiim_get_session_context (context_iiim); if (!c) return; context_iiim->has_focus = TRUE; update_status_window (context_iiim); if (context_iiim->status_window != NULL) { /* enforce to update the status window position */ on_status_toplevel_configure(context_iiim->status_window->toplevel, NULL, context_iiim->status_window); } im_context_switcher_set_focus (context_iiim); /* tell switcher that I'm the current client */ im_context_switcher_set_focus (context_iiim); } #ifdef HAS_IIIM_PROPERTIES get_iiim_settings_property (); context_iiim->kbd_layout = current_iiim_setting.kbd_layout; if (current_iiim_setting_enabled && current_iiim_setting.lang_switch_policy == LS_DESKTOP) { gchar *desktop_lang = get_desktop_lang (); gchar *dt_lang; gchar *dt_le; gchar **elms; if (desktop_lang != NULL) { elms = g_strsplit (desktop_lang, ":", -1); dt_lang = elms[0]; dt_le = elms[1]; } else { dt_lang = NULL; dt_le = NULL; } if ((dt_lang != NULL && dt_le == NULL && strcmp (dt_lang, context_iiim->current_language)) || (dt_le != NULL && context_iiim->current_le == NULL) || (dt_lang != NULL && dt_le != NULL && (strcmp (dt_lang, context_iiim->current_language) || strcmp (dt_le, context_iiim->current_le)))) { im_context_initialize_with_input_language (context_iiim, desktop_lang); } if (dt_lang != NULL) { g_free (desktop_lang); g_strfreev (elms); } } if (is_sync_activation()) { IIIMF_status st; gint conversion_mode = CONV_OFF; gint global_conversion_mode = get_global_conv_mode (); st = iiimcf_get_current_conversion_mode (context_iiim->context, &conversion_mode); if (conversion_mode != global_conversion_mode) { if (global_conversion_mode == 1) im_context_change_conversion_mode (context_iiim, "on"); else if (global_conversion_mode == 0) im_context_change_conversion_mode (context_iiim, "off"); } } #endif /* HAS_IIIM_PROPERTIES */ st = iiimcf_create_seticfocus_event (&ev); if (st != IIIMF_STATUS_SUCCESS) return; if (forward_event (context_iiim, ev, NULL)) iiim_event_dispatch (context_iiim); if (cand_win && !GTK_WIDGET_VISIBLE (cand_win)) { gtk_widget_show (cand_win); } IIIMSetHotkey (context_iiim); if (!has_focus) { if (im_info_switcher_active (context_iiim->iiim_info)) { IIIMCF_language *lang_list; int n_lang; lang_list = iiim_get_languages (context_iiim->iiim_info, &n_lang); im_context_switcher_set_language_list (context_iiim, lang_list, n_lang); im_context_switcher_set_language_engine_list (context_iiim, context_iiim->iiim_info->le_list); im_context_switcher_set_input_language (context_iiim, NULL); } } return; } static void im_context_iiim_focus_out (GtkIMContext * context) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); IIIMF_status st; IIIMCF_event ev; IIIMCF_context c; GtkWidget *cand_win = iiim_get_candidate_window (context_iiim); Atom vkb_target_atom; DEBUG_DO (g_message ("im_context_iiim_focus_out")); if (timeout_id) { g_source_remove (timeout_id); timeout_id = 0; } #ifdef HAS_IIIM_PROPERTIES /* This is needed to ensure gimlet knows the last client conversion mode * to handle on/off policy at language switching "DO NOT CHANGE". */ im_context_switcher_set_conversion_mode (GTK_IM_CONTEXT_IIIM (context)); le_select_window_hide (); #endif /* HAS_IIIM_PROPERTIES */ if (context_iiim->has_focus) { c = iiim_get_session_context (context_iiim); if (!c) return; context_iiim->has_focus = FALSE; update_status_window (context_iiim); IIim_aux_unset_icfocus (context_iiim); DEBUG_DO (g_message ("candidate_window %p", cand_win)); if (cand_win && GTK_WIDGET_VISIBLE (cand_win)) { gtk_widget_hide (cand_win); } } st = iiimcf_create_unseticfocus_event (&ev); if (st != IIIMF_STATUS_SUCCESS) return; if (forward_event (context_iiim, ev, NULL)) iiim_event_dispatch (context_iiim); vkb_target_atom = XInternAtom(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), "_IIIM_VKB_TARGET", False); XSetSelectionOwner(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), vkb_target_atom, GDK_WINDOW_XID(context_iiim->client_window), CurrentTime); return; } static void im_context_iiim_set_cursor_location (GtkIMContext * context, GdkRectangle * area) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); DEBUG_DO (g_message ("im_context_iiim_set_cursor_location")); if (!context_iiim) return; context_iiim->cursor.x = area->x; context_iiim->cursor.y = area->y; context_iiim->cursor.height = area->height; context_iiim->cursor.width = area->width; return; } static void im_context_iiim_set_use_preedit (GtkIMContext * context, gboolean use_preedit) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM (context); DEBUG_DO (g_message ("im_context_iiim_set_use_preedit")); use_preedit = use_preedit != FALSE; if (context_iiim->use_preedit != use_preedit) { context_iiim->use_preedit = use_preedit; reinitialize_sc (context_iiim, TRUE); } return; } static void im_context_iiim_reset (GtkIMContext * context) { GtkIMContextIIIM *context_iiim = GTK_IM_CONTEXT_IIIM(context); IIIMCF_context ctxt; IIIMF_status st; IIIMCF_text text; IIIMCF_event event; gint caret_position, preedit_len = 0; gchar *utf8; DEBUG_DO (g_message ("im_context_iiim_reset")); ctxt = iiim_get_session_context(context_iiim); if (!ctxt) return; /* commit the preedit strings first */ st = iiimcf_get_preedit_text(ctxt, &text, &caret_position); if (st != IIIMF_STATUS_SUCCESS) return; st = iiimcf_get_text_length(text, &preedit_len); if (st != IIIMF_STATUS_SUCCESS) return; if (preedit_len == 0) return; utf8 = utf16_to_utf8(text); if (utf8 != NULL) { /* clear candidate if any */ if (context_iiim->candidate_start == TRUE) { iiim_destroy_candidate_window (context_iiim); context_iiim->candidate_start = FALSE; } /* reset IC */ st = iiimcf_reset_context(ctxt); if (st != IIIMF_STATUS_SUCCESS) return; /* FIXME: workaround to get it working after calling iiimcf_reset_context */ st = iiimcf_create_trigger_notify_event(0, &event); if (st != IIIMF_STATUS_SUCCESS) return; st = iiimcf_forward_event(ctxt, event); st = iiimcf_create_trigger_notify_event(1, &event); if (st != IIIMF_STATUS_SUCCESS) return; st = iiimcf_forward_event(ctxt, event); /* server will send 'commit' once it gets the IC RESET message */ /* forcely handle the 'commit' event in client's event queue */ iiim_event_dispatch (context_iiim); } } static void add_feedback_attr (PangoAttrList * attrs, const gchar * str, const IIIMP_card32 feedback, gint start_pos, gint end_pos) { PangoAttribute *attr; gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str; gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str; DEBUG_DO (g_message ("feedback %d", feedback)); if (feedback == 2 /* Underline */ ) { attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE); attr->start_index = start_index; attr->end_index = end_index; pango_attr_list_change (attrs, attr); } if (feedback == 1 /* Reverse */ ) { attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff); attr->start_index = start_index; attr->end_index = end_index; pango_attr_list_change (attrs, attr); attr = pango_attr_background_new (0, 0, 0); attr->start_index = start_index; attr->end_index = end_index; pango_attr_list_change (attrs, attr); } } static gboolean iiim_check_feedback (const IIIMP_card32 * feedback, gint nfb, const IIIMP_card32 * feedback2, gint nfb2) { gint i; gboolean result = TRUE; if (!feedback) return FALSE; if (nfb != nfb2) return FALSE; for (i = 0; i < nfb; i++) { if (feedback[i] != feedback2[i]) result = FALSE; } return result; } static void im_context_iiim_get_preedit_string (GtkIMContext * context, gchar ** str, PangoAttrList ** attrs, gint * cursor_pos) { GtkIMContextIIIM *im_context_iiim = GTK_IM_CONTEXT_IIIM (context); IIIMF_status st; IIIMCF_text text; gint caret_position; gint preedit_len; gchar *utf8; if (attrs) *attrs = pango_attr_list_new (); if (!im_context_iiim->context) { DEBUG_DO (g_message ("preedit_string context is not initialized")); goto Error; } st = iiimcf_get_preedit_text (im_context_iiim->context, &text, &caret_position); if (st != IIIMF_STATUS_SUCCESS) goto Error; utf8 = utf16_to_utf8 (text); if (attrs) { gint i; gint j; IIIMP_card32 last_visual_feedback = 0; gint start = -1; st = iiimcf_get_text_length (text, &preedit_len); if (st != IIIMF_STATUS_SUCCESS) goto Error; for (i = 0; i < preedit_len; i++) { IIIMP_card16 ch; const IIIMP_card32 *feedback_ids, *feedbacks; int nfb; st = iiimcf_get_char_with_feedback (text, i, &ch, &nfb, &feedback_ids, &feedbacks); if (st != IIIMF_STATUS_SUCCESS) goto Error; for (j = 0; j < nfb; j++) { IIIMP_card32 new_visual_feedback; switch (feedback_ids[j]) { case 0: /* VISUAL ATTRIBUTES */ new_visual_feedback = feedbacks[j]; if (new_visual_feedback != last_visual_feedback) { if (start >= 0) add_feedback_attr (*attrs, utf8, last_visual_feedback, start, i); last_visual_feedback = new_visual_feedback; start = i; } default: break; } } } if (start >= 0) add_feedback_attr (*attrs, utf8, last_visual_feedback, start, i); } if (str) *str = utf8; else g_free (utf8); DEBUG_DO (g_message ("cursor %d", caret_position)); if (cursor_pos) *cursor_pos = caret_position; return; Error: if (str) { *str = g_strdup (""); } if (cursor_pos) { *cursor_pos = 0; } } GtkIMContext * im_context_iiim_new (void) { GtkIMContextIIIM *result; result = GTK_IM_CONTEXT_IIIM (g_object_new (GTK_TYPE_IM_CONTEXT_IIIM, NULL)); return GTK_IM_CONTEXT (result); } /***************************************************************** * Status Window handling * * A status window is a small window attached to the toplevel * that is used to display information to the user about the * current input operation. * * We claim the toplevel's status window for an input context if: * * A) The input context has a toplevel * B) The input context has the focus * C) The input context has an XIC associated with it * * Tracking A) and C) is pretty reliable since we * compute A) and create the XIC for C) ourselves. * For B) we basically have to depend on our callers * calling ::focus-in and ::focus-out at the right time. * * The toplevel is computed by walking up the GdkWindow * hierarchy from context->client_window until we find a * window that is owned by some widget, and then calling * gtk_widget_get_toplevel() on that widget. This should * handle both cases where we might have GdkWindows without widgets, * and cases where GtkWidgets have strange window hierarchies * (like a torn off GtkHandleBox.) * * The status window is visible if and only if there is text * for it; whenever a new GtkIMContextIIIM claims the status * window, we blank out any existing text. We actually only * create a GtkWindow for the status window the first time * it is shown; this is an important optimization when we are * using IIIM with something like a simple compose-key input * method that never needs a status window. *****************************************************************/ /* Called when we no longer need a status window */ static void disclaim_status_window (GtkIMContextIIIM *context_iiim) { if (context_iiim->status_window) { g_assert (context_iiim->status_window->context == context_iiim); status_window_set_text (context_iiim->status_window, ""); context_iiim->status_window->context = NULL; context_iiim->status_window = NULL; } } /* Called when we need a status window */ static void claim_status_window (GtkIMContextIIIM *context_iiim) { if (!context_iiim->status_window && context_iiim->client_widget) { GtkWidget *toplevel = gtk_widget_get_toplevel (context_iiim->client_widget); if (toplevel && GTK_WIDGET_TOPLEVEL (toplevel)) { StatusWindow *status_window = status_window_get (toplevel); if (status_window->context) disclaim_status_window (status_window->context); status_window->context = context_iiim; context_iiim->status_window = status_window; } } } /* Basic call made whenever something changed that might cause * us to need, or not to need a status window. */ static void update_status_window (GtkIMContextIIIM *context_iiim) { if (context_iiim->in_toplevel && context_iiim->has_focus) claim_status_window (context_iiim); else disclaim_status_window (context_iiim); } /* Updates the in_toplevel flag for @context_iiim */ static void update_in_toplevel (GtkIMContextIIIM *context_iiim) { if (context_iiim->client_widget) { GtkWidget *toplevel = gtk_widget_get_toplevel (context_iiim->client_widget); context_iiim->in_toplevel = (toplevel && GTK_WIDGET_TOPLEVEL (toplevel)); } else context_iiim->in_toplevel = FALSE; /* Some paranoia, in case we don't get a focus out */ if (!context_iiim->in_toplevel) context_iiim->has_focus = FALSE; update_status_window (context_iiim); } /* Callback when @widget's toplevel changes. It will always * change from NULL to a window, or a window to NULL; * we use that intermediate NULL state to make sure * that we disclaim the toplevel status window for the old * window. */ static void on_client_widget_hierarchy_changed (GtkWidget *widget, GtkWidget *old_toplevel, GtkIMContextIIIM *context_iiim) { update_in_toplevel (context_iiim); } /* Finds the GtkWidget that owns the window, or if none, the * widget owning the nearest parent that has a widget. */ static GtkWidget * widget_for_window (GdkWindow *window) { while (window) { gpointer user_data; gdk_window_get_user_data (window, &user_data); if (user_data) return user_data; window = gdk_window_get_parent (window); } return NULL; } /* Called when context_iiim->client_window changes; takes care of * removing and/or setting up our watches for the toplevel */ static void update_client_widget (GtkIMContextIIIM *context_iiim) { GtkWidget *new_client_widget = widget_for_window (context_iiim->client_window); if (new_client_widget != context_iiim->client_widget) { if (context_iiim->client_widget) { g_signal_handlers_disconnect_by_func (context_iiim->client_widget, G_CALLBACK (on_client_widget_hierarchy_changed), context_iiim); } context_iiim->client_widget = new_client_widget; if (context_iiim->client_widget) { g_signal_connect (context_iiim->client_widget, "hierarchy-changed", G_CALLBACK (on_client_widget_hierarchy_changed), context_iiim); } update_in_toplevel (context_iiim); } } /* Called when the toplevel is destroyed; frees the status window */ static void on_status_toplevel_destroy (GtkWidget *toplevel, StatusWindow *status_window) { status_window_free (status_window); } /* Called when the screen for the toplevel changes; updates the * screen for the status window to match. */ static void on_status_toplevel_notify_screen (GtkWindow *toplevel, GParamSpec *pspec, StatusWindow *status_window) { if (status_window->window) gtk_window_set_screen (GTK_WINDOW (status_window->window), gtk_widget_get_screen (GTK_WIDGET (toplevel))); } /* Called when the toplevel window is moved; updates the position of * the status window to follow it. */ static gboolean on_status_toplevel_configure (GtkWidget *toplevel, GdkEventConfigure *event, StatusWindow *status_window) { GdkRectangle rect; GtkRequisition requisition; gint y; gint height; if (status_window->window) { height = gdk_screen_get_height (gtk_widget_get_screen (toplevel)); gdk_window_get_frame_extents (toplevel->window, &rect); gtk_widget_size_request (status_window->window, &requisition); if (rect.y + rect.height + requisition.height < height) y = rect.y + rect.height; else y = height - requisition.height; gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y); } return FALSE; } /* Frees a status window and removes its link from the status_windows list */ static void status_window_free (StatusWindow *status_window) { status_windows = g_slist_remove (status_windows, status_window); if (status_window->context) status_window->context->status_window = NULL; g_signal_handlers_disconnect_by_func (status_window->toplevel, G_CALLBACK (on_status_toplevel_destroy), status_window); g_signal_handlers_disconnect_by_func (status_window->toplevel, G_CALLBACK (on_status_toplevel_notify_screen), status_window); g_signal_handlers_disconnect_by_func (status_window->toplevel, G_CALLBACK (on_status_toplevel_configure), status_window); if (status_window->window) gtk_widget_destroy (status_window->window); g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-iiim-status-window", NULL); g_free (status_window); } /* Finds the status window object for a toplevel, creating it if necessary. */ static StatusWindow * status_window_get (GtkWidget *toplevel) { StatusWindow *status_window; status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-iiim-status-window"); if (status_window) return status_window; status_window = g_new0 (StatusWindow, 1); status_window->toplevel = toplevel; status_windows = g_slist_prepend (status_windows, status_window); g_signal_connect (toplevel, "destroy", G_CALLBACK (on_status_toplevel_destroy), status_window); g_signal_connect (toplevel, "configure_event", G_CALLBACK (on_status_toplevel_configure), status_window); g_signal_connect (toplevel, "notify::screen", G_CALLBACK (on_status_toplevel_notify_screen), status_window); g_object_set_data (G_OBJECT (toplevel), "gtk-im-iiim-status-window", status_window); return status_window; } /* Draw the background (normally white) and border for the status window */ static gboolean on_status_window_expose_event (GtkWidget *widget, GdkEventExpose *event) { gdk_draw_rectangle (widget->window, widget->style->base_gc [GTK_STATE_NORMAL], TRUE, 0, 0, widget->allocation.width, widget->allocation.height); gdk_draw_rectangle (widget->window, widget->style->text_gc [GTK_STATE_NORMAL], FALSE, 0, 0, widget->allocation.width - 1, widget->allocation.height - 1); return FALSE; } /* We watch the ::style-set signal for our label widget * and use that to change it's foreground color to match * the 'text' color of the toplevel window. The text/base * pair of colors might be reversed from the fg/bg pair * that are normally used for labels. */ static void on_status_window_style_set (GtkWidget *toplevel, GtkStyle *previous_style, GtkWidget *label) { gint i; for (i = 0; i < 5; i++) gtk_widget_modify_fg (label, i, &toplevel->style->text[i]); } /* Creates the widgets for the status window; called when we * first need to show text for the status window. */ static void status_window_make_window (StatusWindow *status_window) { GtkWidget *window; GtkWidget *status_label; status_window->window = gtk_window_new (GTK_WINDOW_POPUP); window = status_window->window; gtk_window_set_resizable (GTK_WINDOW (window), FALSE); gtk_widget_set_app_paintable (window, TRUE); status_label = gtk_label_new (""); gtk_misc_set_padding (GTK_MISC (status_label), 1, 1); gtk_widget_show (status_label); g_signal_connect (window, "style_set", G_CALLBACK (on_status_window_style_set), status_label); gtk_container_add (GTK_CONTAINER (window), status_label); g_signal_connect (window, "expose_event", G_CALLBACK (on_status_window_expose_event), NULL); gtk_window_set_screen (GTK_WINDOW (status_window->window), gtk_widget_get_screen (status_window->toplevel)); on_status_toplevel_configure (status_window->toplevel, NULL, status_window); } /* Updates the text in the status window, hiding or * showing the window as necessary. */ static void status_window_set_text (StatusWindow *status_window, const gchar *text) { if (status_window == NULL) /* status window is disclaimed */ return; if (text[0]) { GtkWidget *label; if (!status_window->window) status_window_make_window (status_window); label = GTK_BIN (status_window->window)->child; gtk_label_set_text (GTK_LABEL (label), text); gtk_widget_show (status_window->window); } else { if (status_window->window) gtk_widget_hide (status_window->window); } } /** * im_context_iiim_shutdown: * * Destroys all the status windows that are kept by the IIIM contexts. This * function should only be called by the IIIM module exit routine. **/ void im_context_iiim_shutdown (void) { GSList *tmp_list; GtkIIIMInfo *info = NULL; DEBUG_DO (g_message ("shutdown\n")); if (iiim) iiimcf_destroy_handle (iiim); iiimcf_finalize (); iiim_is_initialized = FALSE; while (status_windows) status_window_free (status_windows->data); IIim_aux_shutdown(); tmp_list = open_iiims; while (tmp_list) { info = tmp_list->data; g_signal_handler_disconnect (info->settings, info->status_set); im_info_switcher_shutdown (info); tmp_list = tmp_list->next; } } void im_context_initialize_with_input_language (GtkIMContextIIIM *context_iiim, gchar *new_lang) { IIIMCF_language iiim_lang = NULL; IIIMF_status st; IIIMCF_attr attr; IIIMCF_event ev; gboolean conversion_mode = FALSE; gchar **names = NULL; gchar *le_name = NULL; gint i = 0, n; const IIIMP_card16 *u16idname, *u16hrn, *u16domain; gboolean found_le = FALSE; IIIMCF_input_method *pims; if (context_iiim == NULL || context_iiim->context == NULL) return; if (new_lang) { names = g_strsplit(new_lang, ":", -1); new_lang = names[0]; le_name = names[1]; iiim_lang = get_input_language (context_iiim, new_lang, TRUE); if (iiim_lang != NULL) { g_free (context_iiim->current_language); context_iiim->current_language = g_strdup (new_lang); #ifdef HAS_IIIM_PROPERTIES if (le_name != NULL) { g_free (context_iiim->current_le); context_iiim->current_le = g_strdup (le_name); } #endif /* HAS_IIIM_PROPERTIES */ } else /* invalid new lang */ goto im_lang_error; } else iiim_lang = get_input_language (context_iiim, context_iiim->current_language, FALSE); if (le_name && *le_name) { char *idname = NULL; /* probably le_name consists of 'lename' and 'imename' which are separated by '-' */ const char *ime_delimiter = "-"; char **le_ime = g_strsplit (le_name, ime_delimiter, -1); char *lename = le_ime[0], *imename = le_ime[1]; st = iiimcf_get_supported_input_methods (iiim, &n, &pims); if (st != IIIMF_STATUS_SUCCESS) { g_strfreev (le_ime); goto im_lang_error; } for (i = 0; i < n; i++) { st = iiimcf_get_input_method_desc (pims[i], &u16idname, &u16hrn, &u16domain); if (st != IIIMF_STATUS_SUCCESS) { g_strfreev (le_ime); goto im_lang_error; } #ifdef HAS_IIIM_PROPERTIES idname = format_iiimcf_string (u16idname); #else /* HAS_IIIM_PROPERTIES */ idname = format_iiimcf_string (u16hrn); #endif /* HAS_IIIM_PROPERTIES */ if (idname != NULL && strcmp (lename, idname) == 0) { /* update the pims[i]->imname with the new imname that contains imename */ if (imename) { IIIMP_card16 *imname = format_utf8_string (le_name); iiimcf_set_input_method_name (pims[i], imname); } g_free (idname); found_le = TRUE; break; } if (idname != NULL) { g_free (idname); idname = NULL; } } g_strfreev (le_ime); } /* save conversion mode to restore this after switching */ st = iiimcf_get_current_conversion_mode (context_iiim->context, &conversion_mode); /* to cancel any remaining preedit text */ if (!context_iiim->finalizing) g_signal_emit_by_name (context_iiim, "preedit_changed"); /* clear candidate if any */ if (context_iiim->candidate_start == TRUE) { iiim_destroy_candidate_window (context_iiim); context_iiim->candidate_start = FALSE; } context_iiim->lookup_choice = NULL; st = iiimcf_create_attr (&attr); if (st != IIIMF_STATUS_SUCCESS) goto im_lang_error; if (found_le) iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_METHOD, pims[i]); if (iiim_lang) iiimcf_attr_put_ptr_value (attr, IIIMCF_ATTR_INPUT_LANGUAGE, iiim_lang); #ifdef HAS_IIIM_PROPERTIES iiimcf_attr_put_integer_value (attr, IIIMCF_ATTR_KBD_LAYOUT, context_iiim->kbd_layout); #endif /* HAS_IIIM_PROPERTIES */ st = iiimcf_context_set_attr(context_iiim->context, attr); iiimcf_destroy_attr (attr); if (st != IIIMF_STATUS_SUCCESS) goto im_lang_error; st = iiimcf_create_seticfocus_event (&ev); if (st != IIIMF_STATUS_SUCCESS) goto im_lang_error; forward_event(context_iiim, ev, NULL); if (conversion_mode) { st = iiimcf_create_trigger_notify_event (CONV_ON, &ev); if (st != IIIMF_STATUS_SUCCESS) goto im_lang_error; forward_event(context_iiim, ev, NULL); } iiim_event_dispatch (context_iiim); im_lang_error: g_strfreev(names); } void im_context_change_conversion_mode (GtkIMContextIIIM *context_iiim, gchar *conv_mode) { IIIMCF_event event; if (conv_mode && !strcmp ((gchar*)conv_mode, "on")) { IIIMF_status st; st = iiimcf_create_trigger_notify_event (CONV_ON, &event); if (st != IIIMF_STATUS_SUCCESS) return; } else if (conv_mode && !strcmp ((gchar*)conv_mode, "off")) { IIIMF_status st; st = iiimcf_create_trigger_notify_event (CONV_OFF, &event); if (st != IIIMF_STATUS_SUCCESS) return; } if (forward_event(context_iiim, event, NULL)) iiim_event_dispatch (context_iiim); } GdkScreen* im_info_get_screen (GtkIIIMInfo *info) { if (info == NULL) return NULL; return info->screen; } IIIMCF_handle im_info_get_handle (GtkIIIMInfo *info) { if (info == NULL) return NULL; return info->iiim; } SwitcherInfo* im_info_get_switcher_info (GtkIIIMInfo *info) { if (info == NULL) return NULL; return info->switcher_info; } void im_info_set_switcher_info (GtkIIIMInfo *info, SwitcherInfo *sw_info) { info->switcher_info = sw_info; } /* Aux */ void im_context_aux_set_values (GtkIMContextIIIM *context_iiim, IIIMCF_event ev) { forward_event(context_iiim, ev, NULL); iiim_event_dispatch (context_iiim); } void im_context_aux_get_values (GtkIMContextIIIM *context_iiim, IIIMCF_event ev) { forward_event(context_iiim, ev, NULL); iiim_event_dispatch (context_iiim); } /* Local Variables: */ /* c-file-style: "gnu" */ /* End: */