]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextxim.c
Substantially rework the handling of status windows:
[~andy/gtk] / modules / input / gtkimcontextxim.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2000 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "locale.h"
21 #include <string.h>
22
23 #include "gtk/gtkintl.h"
24 #include "gtk/gtklabel.h"
25 #include "gtk/gtksignal.h"
26 #include "gtk/gtkwindow.h"
27 #include "gtkimcontextxim.h"
28
29 typedef struct _StatusWindow StatusWindow;
30 typedef struct _GtkXIMInfo GtkXIMInfo;
31
32 struct _GtkIMContextXIM
33 {
34   GtkIMContext object;
35
36   GtkXIMInfo *im_info;
37
38   gchar *locale;
39   gchar *mb_charset;
40
41   GdkWindow *client_window;
42   GtkWidget *client_widget;
43
44   /* The status window for this input context; we claim the
45    * status window when we are focused and have created an XIC
46    */
47   StatusWindow *status_window;
48
49   gint preedit_size;
50   gint preedit_length;
51   gunichar *preedit_chars;
52   XIMFeedback *feedbacks;
53
54   gint preedit_cursor;
55   
56   XIMCallback preedit_start_callback;
57   XIMCallback preedit_done_callback;
58   XIMCallback preedit_draw_callback;
59   XIMCallback preedit_caret_callback;
60
61   XIMCallback status_start_callback;
62   XIMCallback status_done_callback;
63   XIMCallback status_draw_callback;
64
65   XIC ic;
66
67   guint filter_key_release : 1;
68   guint use_preedit : 1;
69   guint finalizing : 1;
70   guint in_toplevel : 1;
71   guint has_focus : 1;
72 };
73
74 struct _GtkXIMInfo
75 {
76   GdkScreen *screen;
77   XIM im;
78   char *locale;
79   XIMStyle preedit_style_setting;
80   XIMStyle status_style_setting;
81   XIMStyle style;
82   GtkSettings *settings;
83   gulong status_set;
84   gulong preedit_set;
85   XIMStyles *xim_styles;
86   GSList *ics;
87
88   guint reconnecting :1;
89 };
90
91 /* A context status window; these are kept in the status_windows list. */
92 struct _StatusWindow
93 {
94   GtkWidget *window;
95   
96   /* Toplevel window to which the status window corresponds */
97   GtkWidget *toplevel;
98
99   /* Currently focused GtkIMContextXIM for the toplevel, if any */
100   GtkIMContextXIM *context;
101 };
102
103 static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
104 static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
105 static void     gtk_im_context_xim_finalize           (GObject               *obj);
106 static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
107                                                        GdkWindow             *client_window);
108 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
109                                                        GdkEventKey           *key);
110 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
111 static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
112 static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
113 static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
114                                                        GdkRectangle             *area);
115 static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
116                                                        gboolean               use_preedit);
117 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
118                                                        gchar                **str,
119                                                        PangoAttrList        **attrs,
120                                                        gint                  *cursor_pos);
121
122 static void reinitialize_ic      (GtkIMContextXIM *context_xim);
123 static void set_ic_client_window (GtkIMContextXIM *context_xim,
124                                   GdkWindow       *client_window);
125
126 static void setup_styles (GtkXIMInfo *info);
127
128 static void update_client_widget   (GtkIMContextXIM *context_xim);
129 static void update_status_window   (GtkIMContextXIM *context_xim);
130
131 static StatusWindow *status_window_get      (GtkWidget    *toplevel);
132 static void          status_window_free     (StatusWindow *status_window);
133 static void          status_window_set_text (StatusWindow *status_window,
134                                              const gchar  *text);
135
136 static void xim_destroy_callback   (XIM      xim,
137                                     XPointer client_data,
138                                     XPointer call_data);
139
140 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
141 static GObjectClass *parent_class;
142
143 GType gtk_type_im_context_xim = 0;
144
145 GSList *open_ims = NULL;
146
147 /* List of status windows for different toplevels */
148 static GSList *status_windows = NULL;
149
150 void
151 gtk_im_context_xim_register_type (GTypeModule *type_module)
152 {
153   static const GTypeInfo im_context_xim_info =
154   {
155     sizeof (GtkIMContextXIMClass),
156     (GBaseInitFunc) NULL,
157     (GBaseFinalizeFunc) NULL,
158     (GClassInitFunc) gtk_im_context_xim_class_init,
159     NULL,           /* class_finalize */    
160     NULL,           /* class_data */
161     sizeof (GtkIMContextXIM),
162     0,
163     (GInstanceInitFunc) gtk_im_context_xim_init,
164   };
165
166   gtk_type_im_context_xim = 
167     g_type_module_register_type (type_module,
168                                  GTK_TYPE_IM_CONTEXT,
169                                  "GtkIMContextXIM",
170                                  &im_context_xim_info, 0);
171 }
172
173 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
174                       XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
175 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
176                       XIMStatusNothing | XIMStatusNone)
177 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
178                       XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
179
180 static XIMStyle 
181 choose_better_style (XIMStyle style1, XIMStyle style2) 
182 {
183   XIMStyle s1, s2, u; 
184   
185   if (style1 == 0) return style2;
186   if (style2 == 0) return style1;
187   if ((style1 & (PREEDIT_MASK | STATUS_MASK))
188         == (style2 & (PREEDIT_MASK | STATUS_MASK)))
189     return style1;
190
191   s1 = style1 & PREEDIT_MASK;
192   s2 = style2 & PREEDIT_MASK;
193   u = s1 | s2;
194   if (s1 != s2) {
195     if (u & XIMPreeditCallbacks)
196       return (s1 == XIMPreeditCallbacks) ? style1 : style2;
197     else if (u & XIMPreeditPosition)
198       return (s1 == XIMPreeditPosition) ? style1 :style2;
199     else if (u & XIMPreeditArea)
200       return (s1 == XIMPreeditArea) ? style1 : style2;
201     else if (u & XIMPreeditNothing)
202       return (s1 == XIMPreeditNothing) ? style1 : style2;
203     else if (u & XIMPreeditNone)
204       return (s1 == XIMPreeditNone) ? style1 : style2;
205   } else {
206     s1 = style1 & STATUS_MASK;
207     s2 = style2 & STATUS_MASK;
208     u = s1 | s2;
209     if (u & XIMStatusCallbacks)
210       return (s1 == XIMStatusCallbacks) ? style1 : style2;
211     else if (u & XIMStatusArea)
212       return (s1 == XIMStatusArea) ? style1 : style2;
213     else if (u & XIMStatusNothing)
214       return (s1 == XIMStatusNothing) ? style1 : style2;
215     else if (u & XIMStatusNone)
216       return (s1 == XIMStatusNone) ? style1 : style2;
217   }
218   return 0; /* Get rid of stupid warning */
219 }
220
221 static void
222 reinitialize_all_ics (GtkXIMInfo *info)
223 {
224   GSList *tmp_list;
225
226   for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
227     reinitialize_ic (tmp_list->data);
228 }
229
230 static void
231 status_style_change (GtkXIMInfo *info)
232 {
233   GtkIMStatusStyle status_style;
234   
235   g_object_get (info->settings,
236                 "gtk-im-status-style", &status_style,
237                 NULL);
238   if (status_style == GTK_IM_STATUS_CALLBACK)
239     info->status_style_setting = XIMStatusCallbacks;
240   else if (status_style == GTK_IM_STATUS_NOTHING)
241     info->status_style_setting = XIMStatusNothing;
242   else if (status_style == GTK_IM_STATUS_NONE)
243     info->status_style_setting = XIMStatusNone;
244   else
245     return;
246
247   setup_styles (info);
248   
249   reinitialize_all_ics (info);
250 }
251
252 static void
253 preedit_style_change (GtkXIMInfo *info)
254 {
255   GtkIMPreeditStyle preedit_style;
256   g_object_get (info->settings,
257                 "gtk-im-preedit-style", &preedit_style,
258                 NULL);
259   if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
260     info->preedit_style_setting = XIMPreeditCallbacks;
261   else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
262     info->preedit_style_setting = XIMPreeditNothing;
263   else if (preedit_style == GTK_IM_PREEDIT_NONE)
264     info->preedit_style_setting = XIMPreeditNone;
265   else
266     return;
267
268   setup_styles (info);
269   
270   reinitialize_all_ics (info);
271 }
272
273 static void
274 setup_styles (GtkXIMInfo *info)
275 {
276   int i;
277   unsigned long settings_preference;
278   XIMStyles *xim_styles = info->xim_styles;
279
280   settings_preference = info->status_style_setting|info->preedit_style_setting;
281   info->style = 0;
282   if (xim_styles)
283     {
284       for (i = 0; i < xim_styles->count_styles; i++)
285         if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
286           {
287             if (settings_preference == xim_styles->supported_styles[i])
288               {
289                 info->style = settings_preference;
290                 break;
291               }
292             info->style = choose_better_style (info->style,
293                                                xim_styles->supported_styles[i]);
294           }
295     }
296   if (info->style == 0)
297     info->style = XIMPreeditNothing | XIMStatusNothing;
298 }
299
300 static void
301 setup_im (GtkXIMInfo *info)
302 {
303   XIMValuesList *ic_values = NULL;
304   XIMCallback im_destroy_callback;
305
306   if (info->im == NULL)
307     return;
308
309   im_destroy_callback.client_data = (XPointer)info;
310   im_destroy_callback.callback = (XIMProc)xim_destroy_callback;
311   XSetIMValues (info->im,
312                 XNDestroyCallback, &im_destroy_callback,
313                 NULL);
314
315   XGetIMValues (info->im,
316                 XNQueryInputStyle, &info->xim_styles,
317                 XNQueryICValuesList, &ic_values,
318                 NULL);
319
320   info->settings = gtk_settings_get_for_screen (info->screen);
321
322   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
323                                      "gtk-im-preedit-style"))
324     gtk_settings_install_property (g_param_spec_enum ("gtk-im-preedit-style",
325                                                       _("IM Preedit style"),
326                                                       _("How to draw the input method preedit string"),
327                                                       GTK_TYPE_IM_PREEDIT_STYLE,
328                                                       GTK_IM_PREEDIT_CALLBACK,
329                                                       G_PARAM_READWRITE));
330
331   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
332                                      "gtk-im-status-style"))
333     gtk_settings_install_property (g_param_spec_enum ("gtk-im-status-style",
334                                                       _("IM Status style"),
335                                                       _("How to draw the input method statusbar"),
336                                                       GTK_TYPE_IM_STATUS_STYLE,
337                                                       GTK_IM_STATUS_CALLBACK,
338                                                       G_PARAM_READWRITE));
339
340   info->status_set = g_signal_connect_swapped (info->settings,
341                                                "notify::gtk-im-status-style",
342                                                G_CALLBACK (status_style_change),
343                                                info);
344   info->preedit_set = g_signal_connect_swapped (info->settings,
345                                                 "notify::gtk-im-preedit-style",
346                                                 G_CALLBACK (preedit_style_change),
347                                                 info);
348
349   status_style_change (info);
350   preedit_style_change (info);
351
352 #if 0
353   if (ic_values)
354     {
355       for (i = 0; i < ic_values->count_values; i++)
356         g_print ("%s\n", ic_values->supported_values[i]);
357       for (i = 0; i < xim_styles->count_styles; i++)
358         g_print ("%#x\n", xim_styles->supported_styles[i]);
359     }
360 #endif
361
362   if (ic_values)
363     XFree (ic_values);
364 }
365
366 static void
367 xim_info_display_closed (GdkDisplay *display,
368                          gboolean    is_error,
369                          GtkXIMInfo *info)
370 {
371   GSList *ics, *tmp_list;
372
373   open_ims = g_slist_remove (open_ims, info);
374
375   ics = info->ics;
376   info->ics = NULL;
377
378   for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
379     set_ic_client_window (tmp_list->data, NULL);
380
381   g_slist_free (ics);
382   
383   g_signal_handler_disconnect (info->settings, info->status_set);
384   g_signal_handler_disconnect (info->settings, info->preedit_set);
385   
386   XFree (info->xim_styles->supported_styles);
387   XFree (info->xim_styles);
388   g_free (info->locale);
389
390   if (info->im)
391     XCloseIM (info->im);
392
393   g_free (info);
394 }
395
396 static void
397 xim_instantiate_callback (Display *display, XPointer client_data,
398                           XPointer call_data)
399 {
400   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
401   XIM im = NULL;
402
403   im = XOpenIM (display, NULL, NULL, NULL);
404
405   if (!im)
406     return;
407
408   info->im = im;
409   setup_im (info);
410
411   XUnregisterIMInstantiateCallback (display, NULL, NULL, NULL,
412                                     xim_instantiate_callback,
413                                     (XPointer)info);
414   info->reconnecting = FALSE;
415 }
416
417 /* initialize info->im */
418 static void
419 xim_info_try_im (GtkXIMInfo *info)
420 {
421   GdkScreen *screen = info->screen;
422   GdkDisplay *display = gdk_screen_get_display (screen);
423
424   g_assert (info->im == NULL);
425   if (info->reconnecting)
426     return;
427
428   if (XSupportsLocale ())
429     {
430       if (!XSetLocaleModifiers (""))
431         g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
432       info->im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
433       if (!info->im)
434         {
435           XRegisterIMInstantiateCallback (GDK_DISPLAY_XDISPLAY(display),
436                                           NULL, NULL, NULL,
437                                           xim_instantiate_callback,
438                                           (XPointer)info);
439           info->reconnecting = TRUE;
440           return;
441         }
442       setup_im (info);
443
444       g_signal_connect (display, "closed",
445                         G_CALLBACK (xim_info_display_closed), info);
446     }
447 }
448
449 static void
450 xim_destroy_callback (XIM      xim,
451                       XPointer client_data,
452                       XPointer call_data)
453 {
454   GtkXIMInfo *info = (GtkXIMInfo*)client_data;
455
456   info->im = NULL;
457
458   g_signal_handler_disconnect (info->settings, info->status_set);
459   g_signal_handler_disconnect (info->settings, info->preedit_set);
460
461   reinitialize_all_ics (info);
462   xim_info_try_im (info);
463   return;
464
465
466 static GtkXIMInfo *
467 get_im (GdkWindow *client_window,
468         const char *locale)
469 {
470   GSList *tmp_list;
471   GtkXIMInfo *info;
472   GdkScreen *screen = gdk_drawable_get_screen (client_window);
473
474   info = NULL;
475   tmp_list = open_ims;
476   while (tmp_list)
477     {
478       GtkXIMInfo *tmp_info = tmp_list->data;
479       if (tmp_info->screen == screen &&
480           strcmp (tmp_info->locale, locale) == 0)
481         {
482           if (tmp_info->im)
483             {
484               return tmp_info;
485             }
486           else
487             {
488               tmp_info = tmp_info;
489               break;
490             }
491         }
492       tmp_list = tmp_list->next;
493     }
494
495   if (info == NULL)
496     {
497       info = g_new (GtkXIMInfo, 1);
498       open_ims = g_slist_prepend (open_ims, info);
499
500       info->screen = screen;
501       info->locale = g_strdup (locale);
502       info->xim_styles = NULL;
503       info->preedit_style_setting = 0;
504       info->status_style_setting = 0;
505       info->settings = NULL;
506       info->preedit_set = 0;
507       info->status_set = 0;
508       info->ics = NULL;
509       info->reconnecting = FALSE;
510       info->im = NULL;
511     }
512
513   xim_info_try_im (info);
514   return info;
515 }
516
517 static void
518 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
519 {
520   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
521   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
522
523   parent_class = g_type_class_peek_parent (class);
524
525   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
526   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
527   im_context_class->reset = gtk_im_context_xim_reset;
528   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
529   im_context_class->focus_in = gtk_im_context_xim_focus_in;
530   im_context_class->focus_out = gtk_im_context_xim_focus_out;
531   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
532   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
533   gobject_class->finalize = gtk_im_context_xim_finalize;
534 }
535
536 static void
537 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
538 {
539   im_context_xim->use_preedit = TRUE;
540   im_context_xim->filter_key_release = FALSE;
541   im_context_xim->finalizing = FALSE;
542   im_context_xim->has_focus = FALSE;
543   im_context_xim->in_toplevel = FALSE;
544 }
545
546 static void
547 gtk_im_context_xim_finalize (GObject *obj)
548 {
549   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
550
551   context_xim->finalizing = TRUE;
552
553   set_ic_client_window (context_xim, NULL);
554
555   g_free (context_xim->locale);
556   g_free (context_xim->mb_charset);
557 }
558
559 static void
560 reinitialize_ic (GtkIMContextXIM *context_xim)
561 {
562   if (context_xim->ic)
563     {
564       XDestroyIC (context_xim->ic);
565       context_xim->ic = NULL;
566       update_status_window (context_xim);
567
568       if (context_xim->preedit_length)
569         {
570           context_xim->preedit_length = 0;
571           if (!context_xim->finalizing)
572             g_signal_emit_by_name (context_xim, "preedit_changed");
573         }
574     }
575   /* 
576      reset filter_key_release flag, otherwise keystrokes will be doubled
577      until reconnecting to XIM.
578   */
579   context_xim->filter_key_release = FALSE;
580 }
581
582 static void
583 set_ic_client_window (GtkIMContextXIM *context_xim,
584                       GdkWindow       *client_window)
585 {
586   reinitialize_ic (context_xim);
587   if (context_xim->client_window)
588     {
589       context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
590       context_xim->im_info = NULL;
591     }
592   
593   context_xim->client_window = client_window;
594
595   if (context_xim->client_window)
596     {
597       context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
598       context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
599     }
600   
601   update_client_widget (context_xim);
602 }
603
604 static void
605 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
606                                       GdkWindow             *client_window)
607 {
608   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
609
610   set_ic_client_window (context_xim, client_window);
611 }
612
613 GtkIMContext *
614 gtk_im_context_xim_new (void)
615 {
616   GtkIMContextXIM *result;
617   const gchar *charset;
618
619   result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
620
621   result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
622   
623   g_get_charset (&charset);
624   result->mb_charset = g_strdup (charset);
625
626   return GTK_IM_CONTEXT (result);
627 }
628
629 static char *
630 mb_to_utf8 (GtkIMContextXIM *context_xim,
631             const char      *str)
632 {
633   GError *error = NULL;
634   gchar *result;
635
636   if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
637     result = g_strdup (str);
638   else
639     {
640       result = g_convert (str, -1,
641                           "UTF-8", context_xim->mb_charset,
642                           NULL, NULL, &error);
643       if (!result)
644         {
645           g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
646           g_error_free (error);
647         }
648     }
649   
650   return result;
651 }
652
653 static gboolean
654 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
655                                     GdkEventKey  *event)
656 {
657   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
658   XIC ic = gtk_im_context_xim_get_ic (context_xim);
659   gchar static_buffer[256];
660   gchar *buffer = static_buffer;
661   gint buffer_size = sizeof(static_buffer) - 1;
662   gint num_bytes = 0;
663   KeySym keysym;
664   Status status;
665   gboolean result = FALSE;
666   GdkWindow *root_window = gdk_screen_get_root_window (gdk_drawable_get_screen (event->window));
667
668   XKeyPressedEvent xevent;
669
670   if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
671     return FALSE;
672
673   xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
674   xevent.serial = 0;            /* hope it doesn't matter */
675   xevent.send_event = event->send_event;
676   xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
677   xevent.window = GDK_DRAWABLE_XID (event->window);
678   xevent.root = GDK_DRAWABLE_XID (root_window);
679   xevent.subwindow = xevent.window;
680   xevent.time = event->time;
681   xevent.x = xevent.x_root = 0;
682   xevent.y = xevent.y_root = 0;
683   xevent.state = event->state;
684   xevent.keycode = event->hardware_keycode;
685   xevent.same_screen = True;
686   
687   if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
688     return TRUE;
689   
690  again:
691   if (ic)
692     num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
693   else
694     {
695       num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
696       status = XLookupBoth;
697     }
698
699   if (status == XBufferOverflow)
700     {
701       buffer_size = num_bytes;
702       if (buffer != static_buffer) 
703         g_free (buffer);
704       buffer = g_malloc (num_bytes + 1);
705       goto again;
706     }
707
708   /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
709    * here ... do input methods actually change the keysym? we can't really
710    * feed it back to accelerator processing at this point...
711    */
712   if (status == XLookupChars || status == XLookupBoth)
713     {
714       char *result_utf8;
715
716       buffer[num_bytes] = '\0';
717
718       result_utf8 = mb_to_utf8 (context_xim, buffer);
719       if (result_utf8)
720         {
721           if ((guchar)result_utf8[0] >= 0x20 &&
722               result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
723                                        * control characters into strings
724                                        */
725             {
726               g_signal_emit_by_name (context, "commit", result_utf8);
727               result = TRUE;
728             }
729           
730           g_free (result_utf8);
731         }
732     }
733
734   if (buffer != static_buffer) 
735     g_free (buffer);
736
737   return result;
738 }
739
740 static void
741 gtk_im_context_xim_focus_in (GtkIMContext *context)
742 {
743   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
744
745   if (!context_xim->has_focus)
746     {
747       XIC ic = gtk_im_context_xim_get_ic (context_xim);
748
749       context_xim->has_focus = TRUE;
750       update_status_window (context_xim);
751       
752       if (ic)
753         XSetICFocus (ic);
754     }
755
756   return;
757 }
758
759 static void
760 gtk_im_context_xim_focus_out (GtkIMContext *context)
761 {
762   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
763
764   if (context_xim->has_focus)
765     {
766       XIC ic = gtk_im_context_xim_get_ic (context_xim);
767       
768       context_xim->has_focus = FALSE;
769       update_status_window (context_xim);
770   
771       if (ic)
772         XUnsetICFocus (ic);
773     }
774
775   return;
776 }
777
778 static void
779 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
780                                         GdkRectangle *area)
781 {
782   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
783   XIC ic = gtk_im_context_xim_get_ic (context_xim);
784
785   XVaNestedList preedit_attr;
786   XPoint          spot;
787
788   if (!ic)
789     return;
790
791   spot.x = area->x;
792   spot.y = area->y;
793
794   preedit_attr = XVaCreateNestedList (0,
795                                       XNSpotLocation, &spot,
796                                       0);
797   XSetICValues (ic,
798                 XNPreeditAttributes, preedit_attr,
799                 NULL);
800   XFree(preedit_attr);
801
802   return;
803 }
804
805 static void
806 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
807                                     gboolean      use_preedit)
808 {
809   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
810
811   use_preedit = use_preedit != FALSE;
812
813   if (context_xim->use_preedit != use_preedit)
814     {
815       context_xim->use_preedit = use_preedit;
816       reinitialize_ic (context_xim);
817     }
818
819   return;
820 }
821
822 static void
823 gtk_im_context_xim_reset (GtkIMContext *context)
824 {
825   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
826   XIC ic = gtk_im_context_xim_get_ic (context_xim);
827   gchar *result;
828
829   /* restore conversion state after resetting ic later */
830   XIMPreeditState preedit_state = XIMPreeditUnKnown;
831   XVaNestedList preedit_attr;
832   gboolean have_preedit_state = FALSE;
833
834   if (!ic)
835     return;
836   
837
838   if (context_xim->preedit_length == 0)
839     return;
840
841   preedit_attr = XVaCreateNestedList(0,
842                                      XNPreeditState, &preedit_state,
843                                      0);
844   if (!XGetICValues(ic,
845                     XNPreeditAttributes, preedit_attr,
846                     NULL))
847     have_preedit_state = TRUE;
848
849   XFree(preedit_attr);
850
851   result = XmbResetIC (ic);
852
853   preedit_attr = XVaCreateNestedList(0,
854                                      XNPreeditState, preedit_state,
855                                      0);
856   if (have_preedit_state)
857     XSetICValues(ic,
858                  XNPreeditAttributes, preedit_attr,
859                  NULL);
860
861   XFree(preedit_attr);
862
863   if (result)
864     {
865       char *result_utf8 = mb_to_utf8 (context_xim, result);
866       if (result_utf8)
867         {
868           g_signal_emit_by_name (context, "commit", result_utf8);
869           g_free (result_utf8);
870         }
871     }
872
873   if (context_xim->preedit_length)
874     {
875       context_xim->preedit_length = 0;
876       g_signal_emit_by_name (context, "preedit_changed");
877     }
878
879   XFree (result);
880 }
881
882 /* Mask of feedback bits that we render
883  */
884 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
885
886 static void
887 add_feedback_attr (PangoAttrList *attrs,
888                    const gchar   *str,
889                    XIMFeedback    feedback,
890                    gint           start_pos,
891                    gint           end_pos)
892 {
893   PangoAttribute *attr;
894   
895   gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
896   gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
897
898   if (feedback & XIMUnderline)
899     {
900       attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
901       attr->start_index = start_index;
902       attr->end_index = end_index;
903
904       pango_attr_list_change (attrs, attr);
905     }
906
907   if (feedback & XIMReverse)
908     {
909       attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
910       attr->start_index = start_index;
911       attr->end_index = end_index;
912
913       pango_attr_list_change (attrs, attr);
914
915       attr = pango_attr_background_new (0, 0, 0);
916       attr->start_index = start_index;
917       attr->end_index = end_index;
918
919       pango_attr_list_change (attrs, attr);
920     }
921
922   if (feedback & ~FEEDBACK_MASK)
923     g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
924 }
925
926 static void     
927 gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
928                                        gchar         **str,
929                                        PangoAttrList **attrs,
930                                        gint           *cursor_pos)
931 {
932   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
933   gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
934
935   if (attrs)
936     {
937       int i;
938       XIMFeedback last_feedback = 0;
939       gint start = -1;
940       
941       *attrs = pango_attr_list_new ();
942
943       for (i = 0; i < context_xim->preedit_length; i++)
944         {
945           XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
946           if (new_feedback != last_feedback)
947             {
948               if (start >= 0)
949                 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
950               
951               last_feedback = new_feedback;
952               start = i;
953             }
954         }
955
956       if (start >= 0)
957         add_feedback_attr (*attrs, utf8, last_feedback, start, i);
958     }
959
960   if (str)
961     *str = utf8;
962   else
963     g_free (utf8);
964
965   if (cursor_pos)
966     *cursor_pos = context_xim->preedit_cursor;
967 }
968
969 static void
970 preedit_start_callback (XIC      xic,
971                         XPointer client_data,
972                         XPointer call_data)
973 {
974   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
975   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
976   
977   if (!context_xim->finalizing)
978     g_signal_emit_by_name (context, "preedit_start");
979 }                    
980
981 static void
982 preedit_done_callback (XIC      xic,
983                      XPointer client_data,
984                      XPointer call_data)
985 {
986   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
987   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
988
989   if (!context_xim->finalizing)
990     g_signal_emit_by_name (context, "preedit_end");  
991 }                    
992
993 static gint
994 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
995 {
996   gint text_length = 0;
997   GError *error = NULL;
998   gchar *result = NULL;
999
1000   if (xim_text && xim_text->string.multi_byte)
1001     {
1002       if (xim_text->encoding_is_wchar)
1003         {
1004           g_warning ("Wide character return from Xlib not currently supported");
1005           *text = NULL;
1006           return 0;
1007         }
1008
1009       if (strcmp (context->mb_charset, "UTF-8") == 0)
1010         result = g_strdup (xim_text->string.multi_byte);
1011       else
1012         result = g_convert (xim_text->string.multi_byte,
1013                             -1,
1014                             "UTF-8",
1015                             context->mb_charset,
1016                             NULL, NULL, &error);
1017       
1018       if (result)
1019         {
1020           text_length = g_utf8_strlen (result, -1);
1021           
1022           if (text_length != xim_text->length)
1023             {
1024               g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
1025             }
1026         }
1027       else
1028         {
1029           g_warning ("Error converting text from IM to UCS-4: %s", error->message);
1030           g_error_free (error);
1031
1032           *text = NULL;
1033           return 0;
1034         }
1035
1036       *text = result;
1037       return text_length;
1038     }
1039   else
1040     {
1041       *text = NULL;
1042       return 0;
1043     }
1044 }
1045
1046 static void
1047 preedit_draw_callback (XIC                           xic, 
1048                        XPointer                      client_data,
1049                        XIMPreeditDrawCallbackStruct *call_data)
1050 {
1051   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1052
1053   XIMText *new_xim_text = call_data->text;
1054   gint new_text_length;
1055   gunichar *new_text = NULL;
1056   gint i;
1057   gint diff;
1058   gint new_length;
1059   gchar *tmp;
1060   
1061   gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
1062   gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
1063
1064   context->preedit_cursor = call_data->caret;
1065   
1066   if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
1067     g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
1068                call_data->chg_first, call_data->chg_length, context->preedit_length);
1069
1070   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
1071   if (tmp)
1072     {
1073       new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
1074       g_free (tmp);
1075     }
1076   
1077   diff = new_text_length - chg_length;
1078   new_length = context->preedit_length + diff;
1079
1080   if (new_length > context->preedit_size)
1081     {
1082       context->preedit_size = new_length;
1083       context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
1084       context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
1085     }
1086
1087   if (diff < 0)
1088     {
1089       for (i = chg_first + chg_length ; i < context->preedit_length; i++)
1090         {
1091           context->preedit_chars[i + diff] = context->preedit_chars[i];
1092           context->feedbacks[i + diff] = context->feedbacks[i];
1093         }
1094     }
1095   else
1096     {
1097       for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
1098         {
1099           context->preedit_chars[i + diff] = context->preedit_chars[i];
1100           context->feedbacks[i + diff] = context->feedbacks[i];
1101         }
1102     }
1103
1104   for (i = 0; i < new_text_length; i++)
1105     {
1106       context->preedit_chars[chg_first + i] = new_text[i];
1107       context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
1108     }
1109
1110   context->preedit_length += diff;
1111
1112   if (new_text)
1113     g_free (new_text);
1114
1115   if (!context->finalizing)
1116     g_signal_emit_by_name (context, "preedit_changed");
1117 }
1118     
1119
1120 static void
1121 preedit_caret_callback (XIC                            xic,
1122                         XPointer                       client_data,
1123                         XIMPreeditCaretCallbackStruct *call_data)
1124 {
1125   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1126   
1127   if (call_data->direction == XIMAbsolutePosition)
1128     {
1129       context->preedit_cursor = call_data->position;
1130       if (!context->finalizing)
1131         g_signal_emit_by_name (context, "preedit_changed");
1132     }
1133   else
1134     {
1135       g_warning ("Caret movement command: %d %d %d not supported",
1136                  call_data->position, call_data->direction, call_data->style);
1137     }
1138 }            
1139
1140 static void
1141 status_start_callback (XIC      xic,
1142                        XPointer client_data,
1143                        XPointer call_data)
1144 {
1145   return;
1146
1147
1148 static void
1149 status_done_callback (XIC      xic,
1150                       XPointer client_data,
1151                       XPointer call_data)
1152 {
1153   return;
1154 }
1155
1156 static void
1157 status_draw_callback (XIC      xic,
1158                       XPointer client_data,
1159                       XIMStatusDrawCallbackStruct *call_data)
1160 {
1161   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1162
1163   if (call_data->type == XIMTextType)
1164     {
1165       gchar *text;
1166       xim_text_to_utf8 (context, call_data->data.text, &text);
1167
1168       if (context->status_window)
1169         status_window_set_text (context->status_window, text ? text : "");
1170     }
1171   else                          /* bitmap */
1172     {
1173       g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
1174     }
1175 }
1176
1177 static XVaNestedList
1178 set_preedit_callback (GtkIMContextXIM *context_xim)
1179 {
1180   context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
1181   context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
1182   context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
1183   context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
1184   context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
1185   context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
1186   context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
1187   context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
1188   return XVaCreateNestedList (0,
1189                               XNPreeditStartCallback, &context_xim->preedit_start_callback,
1190                               XNPreeditDoneCallback, &context_xim->preedit_done_callback,
1191                               XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
1192                               XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
1193                               NULL);
1194 }
1195
1196 static XVaNestedList
1197 set_status_callback (GtkIMContextXIM *context_xim)
1198 {
1199   context_xim->status_start_callback.client_data = (XPointer)context_xim;
1200   context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
1201   context_xim->status_done_callback.client_data = (XPointer)context_xim;
1202   context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
1203   context_xim->status_draw_callback.client_data = (XPointer)context_xim;
1204   context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
1205           
1206   return XVaCreateNestedList (0,
1207                               XNStatusStartCallback, &context_xim->status_start_callback,
1208                               XNStatusDoneCallback, &context_xim->status_done_callback,
1209                               XNStatusDrawCallback, &context_xim->status_draw_callback,
1210                               NULL);
1211 }
1212
1213
1214 static XIC
1215 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
1216 {
1217   if (context_xim->im_info == NULL || context_xim->im_info->im == NULL)
1218     return NULL;
1219
1220   if (!context_xim->ic)
1221     {
1222       const char *name1 = NULL;
1223       XVaNestedList list1 = NULL;
1224       const char *name2 = NULL;
1225       XVaNestedList list2 = NULL;
1226       XIMStyle im_style = 0;
1227       XIC xic = 0;
1228
1229       if (context_xim->use_preedit &&
1230           (context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
1231         {
1232           im_style |= XIMPreeditCallbacks;
1233           name1 = XNPreeditAttributes;
1234           list1 = set_preedit_callback (context_xim);
1235         }
1236       else if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditNone)
1237         im_style |= XIMPreeditNone;
1238       else
1239         im_style |= XIMPreeditNothing;
1240
1241       if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
1242         {
1243           im_style |= XIMStatusCallbacks;
1244           if (name1 == NULL)
1245             {
1246               name1 = XNStatusAttributes;
1247               list1 = set_status_callback (context_xim);
1248             }
1249           else
1250             {
1251               name2 = XNStatusAttributes;
1252               list2 = set_status_callback (context_xim);
1253             }
1254         }
1255       else if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusNone)
1256         im_style |= XIMStatusNone;
1257       else
1258         im_style |= XIMStatusNothing;
1259
1260       xic = XCreateIC (context_xim->im_info->im,
1261                        XNInputStyle, im_style,
1262                        XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
1263                        name1, list1,
1264                        name2, list2,
1265                        NULL);
1266       if (list1)
1267         XFree (list1);
1268       if (list2)
1269         XFree (list2);
1270
1271       if (xic)
1272         {
1273           /* Don't filter key released events with XFilterEvents unless
1274            * input methods ask for. This is a workaround for Solaris input
1275            * method bug in C and European locales. It doubles each key
1276            * stroke if both key pressed and released events are filtered.
1277            * (bugzilla #81759)
1278            */
1279           guint32 mask = 0;
1280           XGetICValues (xic,
1281                         XNFilterEvents, &mask,
1282                         NULL);
1283           context_xim->filter_key_release = (mask & KeyReleaseMask) != 0;
1284         }
1285       
1286       context_xim->ic = xic;
1287
1288       update_status_window (context_xim);
1289       
1290       if (xic && context_xim->has_focus)
1291         XSetICFocus (xic);
1292     }
1293   return context_xim->ic;
1294 }
1295
1296 /*****************************************************************
1297  * Status Window handling
1298  *
1299  * A status window is a small window attached to the toplevel
1300  * that is used to display information to the user about the
1301  * current input operation.
1302  *
1303  * We claim the toplevel's status window for an input context if:
1304  *
1305  * A) The input context has a toplevel
1306  * B) The input context has the focus
1307  * C) The input context has an XIC associated with it
1308  *
1309  * Tracking A) and C) is pretty reliable since we
1310  * compute A) and create the XIC for C) ourselves.
1311  * For B) we basically have to depend on our callers
1312  * calling ::focus-in and ::focus-out at the right time.
1313  *
1314  * The toplevel is computed by walking up the GdkWindow
1315  * hierarchy from context->client_window until we find a
1316  * window that is owned by some widget, and then calling
1317  * gtk_widget_get_toplevel() on that widget. This should
1318  * handle both cases where we might have GdkWindows without widgets,
1319  * and cases where GtkWidgets have strange window hierarchies
1320  * (like a torn off GtkHandleBox.)
1321  *
1322  * The status window is visible if and only if there is text
1323  * for it; whenever a new GtkIMContextXIM claims the status
1324  * window, we blank out any existing text. We actually only
1325  * create a GtkWindow for the status window the first time
1326  * it is shown; this is an important optimization when we are
1327  * using XIM with something like a simple compose-key input
1328  * method that never needs a status window.
1329  *****************************************************************/
1330
1331 /* Called when we no longer need a status window
1332 */
1333 static void
1334 disclaim_status_window (GtkIMContextXIM *context_xim)
1335 {
1336   if (context_xim->status_window)
1337     {
1338       g_assert (context_xim->status_window->context == context_xim);
1339
1340       status_window_set_text (context_xim->status_window, "");
1341       
1342       context_xim->status_window->context = NULL;
1343       context_xim->status_window = NULL;
1344     }
1345 }
1346
1347 /* Called when we need a status window
1348  */
1349 static void
1350 claim_status_window (GtkIMContextXIM *context_xim)
1351 {
1352   if (!context_xim->status_window && context_xim->client_widget)
1353     {
1354       GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
1355       if (toplevel && GTK_WIDGET_TOPLEVEL (toplevel))
1356         {
1357           StatusWindow *status_window = status_window_get (toplevel);
1358
1359           if (status_window->context)
1360             disclaim_status_window (status_window->context);
1361
1362           status_window->context = context_xim;
1363           context_xim->status_window = status_window;
1364         }
1365     }
1366 }
1367
1368 /* Basic call made whenever something changed that might cause
1369  * us to need, or not to need a status window.
1370  */
1371 static void
1372 update_status_window (GtkIMContextXIM *context_xim)
1373 {
1374   if (context_xim->ic && context_xim->in_toplevel && context_xim->has_focus)
1375     claim_status_window (context_xim);
1376   else
1377     disclaim_status_window (context_xim);
1378 }
1379
1380 /* Updates the in_toplevel flag for @context_xim
1381  */
1382 static void
1383 update_in_toplevel (GtkIMContextXIM *context_xim)
1384 {
1385   if (context_xim->client_widget)
1386     {
1387       GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
1388       
1389       context_xim->in_toplevel = (toplevel && GTK_WIDGET_TOPLEVEL (toplevel));
1390     }
1391   else
1392     context_xim->in_toplevel = FALSE;
1393
1394   /* Some paranoia, in case we don't get a focus out */
1395   if (!context_xim->in_toplevel)
1396     context_xim->has_focus = FALSE;
1397   
1398   update_status_window (context_xim);
1399 }
1400
1401 /* Callback when @widget's toplevel changes. It will always
1402  * change from NULL to a window, or a window to NULL;
1403  * we use that intermediate NULL state to make sure
1404  * that we disclaim the toplevel status window for the old
1405  * window.
1406  */
1407 static void
1408 on_client_widget_hierarchy_changed (GtkWidget       *widget,
1409                                     GtkWidget       *old_toplevel,
1410                                     GtkIMContextXIM *context_xim)
1411 {
1412   update_in_toplevel (context_xim);
1413 }
1414
1415 /* Finds the GtkWidget that owns the window, or if none, the
1416  * widget owning the nearest parent that has a widget.
1417  */
1418 static GtkWidget *
1419 widget_for_window (GdkWindow *window)
1420 {
1421   while (window)
1422     {
1423       gpointer user_data;
1424       gdk_window_get_user_data (window, &user_data);
1425       if (user_data)
1426         return user_data;
1427
1428       window = gdk_window_get_parent (window);
1429     }
1430
1431   return NULL;
1432 }
1433
1434 /* Called when context_xim->client_window changes; takes care of
1435  * removing and/or setting up our watches for the toplevel
1436  */
1437 static void
1438 update_client_widget (GtkIMContextXIM *context_xim)
1439 {
1440   GtkWidget *new_client_widget = widget_for_window (context_xim->client_window);
1441
1442   if (new_client_widget != context_xim->client_widget)
1443     {
1444       if (context_xim->client_widget)
1445         {
1446           g_signal_handlers_disconnect_by_func (context_xim->client_widget,
1447                                                 G_CALLBACK (on_client_widget_hierarchy_changed),
1448                                                 context_xim);
1449         }
1450       context_xim->client_widget = new_client_widget;
1451       if (context_xim->client_widget)
1452         {
1453           g_signal_connect (context_xim->client_widget, "hierarchy-changed",
1454                             G_CALLBACK (on_client_widget_hierarchy_changed),
1455                             context_xim);
1456         }
1457
1458       update_in_toplevel (context_xim);
1459     }
1460 }
1461
1462 /* Called when the toplevel is destroyed; frees the status window
1463  */
1464 static void
1465 on_status_toplevel_destroy (GtkWidget    *toplevel,
1466                             StatusWindow *status_window)
1467 {
1468   status_window_free (status_window);
1469 }
1470
1471 /* Called when the screen for the toplevel changes; updates the
1472  * screen for the status window to match.
1473  */
1474 static void
1475 on_status_toplevel_notify_screen (GtkWindow    *toplevel,
1476                                   GParamSpec   *pspec,
1477                                   StatusWindow *status_window)
1478 {
1479   if (status_window->window)
1480     gtk_window_set_screen (GTK_WINDOW (status_window->window),
1481                            gtk_widget_get_screen (GTK_WIDGET (toplevel)));
1482 }
1483
1484 /* Called when the toplevel window is moved; updates the position of
1485  * the status window to follow it.
1486  */
1487 static gboolean
1488 on_status_toplevel_configure (GtkWidget         *toplevel,
1489                               GdkEventConfigure *event,
1490                               StatusWindow      *status_window)
1491 {
1492   GdkRectangle rect;
1493   GtkRequisition requisition;
1494   gint y;
1495   gint height;
1496
1497   if (status_window->window)
1498     {
1499       height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
1500   
1501       gdk_window_get_frame_extents (toplevel->window, &rect);
1502       gtk_widget_size_request (status_window->window, &requisition);
1503       
1504       if (rect.y + rect.height + requisition.height < height)
1505         y = rect.y + rect.height;
1506       else
1507         y = height - requisition.height;
1508       
1509       gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1510     }
1511
1512   return FALSE;
1513 }
1514
1515 /* Frees a status window and removes its link from the status_windows list
1516  */
1517 static void
1518 status_window_free (StatusWindow *status_window)
1519 {
1520   status_windows = g_slist_remove (status_windows, status_window);
1521
1522   if (status_window->context)
1523     status_window->context->status_window = NULL;
1524  
1525   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1526                                         G_CALLBACK (on_status_toplevel_destroy),
1527                                         status_window);
1528   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1529                                         G_CALLBACK (on_status_toplevel_notify_screen),
1530                                         status_window);
1531   g_signal_handlers_disconnect_by_func (status_window->toplevel,
1532                                         G_CALLBACK (on_status_toplevel_configure),
1533                                         status_window);
1534
1535   if (status_window->window)
1536     gtk_widget_destroy (status_window->window);
1537   
1538   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1539  
1540   g_free (status_window);
1541 }
1542
1543 /* Finds the status window object for a toplevel, creating it if necessary.
1544  */
1545 static StatusWindow *
1546 status_window_get (GtkWidget *toplevel)
1547 {
1548   StatusWindow *status_window;
1549
1550   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1551   if (status_window)
1552     return status_window;
1553   
1554   status_window = g_new0 (StatusWindow, 1);
1555   status_window->toplevel = toplevel;
1556
1557   status_windows = g_slist_prepend (status_windows, status_window);
1558
1559   g_signal_connect (toplevel, "destroy",
1560                     G_CALLBACK (on_status_toplevel_destroy),
1561                     status_window);
1562   g_signal_connect (toplevel, "configure_event",
1563                     G_CALLBACK (on_status_toplevel_configure),
1564                     status_window);
1565   g_signal_connect (toplevel, "notify::screen",
1566                     G_CALLBACK (on_status_toplevel_notify_screen),
1567                     status_window);
1568   
1569   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1570
1571   return status_window;
1572 }
1573
1574 /* Draw the background (normally white) and border for the status window
1575  */
1576 static gboolean
1577 on_status_window_expose_event (GtkWidget      *widget,
1578                                GdkEventExpose *event)
1579 {
1580   gdk_draw_rectangle (widget->window,
1581                       widget->style->base_gc [GTK_STATE_NORMAL],
1582                       TRUE,
1583                       0, 0,
1584                       widget->allocation.width, widget->allocation.height);
1585   gdk_draw_rectangle (widget->window,
1586                       widget->style->text_gc [GTK_STATE_NORMAL],
1587                       FALSE,
1588                       0, 0,
1589                       widget->allocation.width - 1, widget->allocation.height - 1);
1590
1591   return FALSE;
1592 }
1593
1594 /* We watch the ::style-set signal for our label widget
1595  * and use that to change it's foreground color to match
1596  * the 'text' color of the toplevel window. The text/base
1597  * pair of colors might be reversed from the fg/bg pair
1598  * that are normally used for labels.
1599  */
1600 static void
1601 on_status_window_style_set (GtkWidget *toplevel,
1602                             GtkStyle  *previous_style,
1603                             GtkWidget *label)
1604 {
1605   gint i;
1606   
1607   for (i = 0; i < 5; i++)
1608     gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
1609 }
1610
1611 /* Creates the widgets for the status window; called when we
1612  * first need to show text for the status window.
1613  */
1614 static void
1615 status_window_make_window (StatusWindow *status_window)
1616 {
1617   GtkWidget *window;
1618   GtkWidget *status_label;
1619   
1620   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1621   window = status_window->window;
1622
1623   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
1624   gtk_widget_set_app_paintable (window, TRUE);
1625
1626   status_label = gtk_label_new ("");
1627   gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
1628   gtk_widget_show (status_label);
1629   
1630   g_signal_connect (window, "style_set",
1631                     G_CALLBACK (on_status_window_style_set), status_label);
1632   gtk_container_add (GTK_CONTAINER (window), status_label);
1633   
1634   g_signal_connect (window, "expose_event",
1635                     G_CALLBACK (on_status_window_expose_event), NULL);
1636   
1637   gtk_window_set_screen (GTK_WINDOW (status_window->window),
1638                          gtk_widget_get_screen (status_window->toplevel));
1639
1640   on_status_toplevel_configure (status_window->toplevel, NULL, status_window);
1641 }
1642
1643 /* Updates the text in the status window, hiding or
1644  * showing the window as necessary.
1645  */
1646 static void
1647 status_window_set_text (StatusWindow *status_window,
1648                         const gchar  *text)
1649 {
1650   if (text[0])
1651     {
1652       GtkWidget *label;
1653       
1654       if (!status_window->window)
1655         status_window_make_window (status_window);
1656       
1657       label = GTK_BIN (status_window->window)->child;
1658       gtk_label_set_text (GTK_LABEL (label), text);
1659   
1660       gtk_widget_show (status_window->window);
1661     }
1662   else
1663     {
1664       if (status_window->window)
1665         gtk_widget_hide (status_window->window);
1666     }
1667 }
1668
1669 /**
1670  * gtk_im_context_xim_shutdown:
1671  * 
1672  * Destroys all the status windows that are kept by the XIM contexts.  This
1673  * function should only be called by the XIM module exit routine.
1674  **/
1675 void
1676 gtk_im_context_xim_shutdown (void)
1677 {
1678   while (status_windows)
1679     status_window_free (status_windows->data);
1680 }