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