]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextxim.c
Fix #90661: add im_info->im switch at the top of setup_im() and
[~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
31 struct _GtkXIMInfo
32 {
33   GdkScreen *screen;
34   XIM im;
35   char *locale;
36   XIMStyle preedit_style_setting;
37   XIMStyle status_style_setting;
38   XIMStyle style;
39   GtkSettings *settings;
40   gulong status_set;
41   gulong preedit_set;
42   XIMStyles *xim_styles;
43   GSList *ics;
44 };
45
46 /* A context status window; these are kept in the status_windows list. */
47 struct _StatusWindow
48 {
49   GtkWidget *window;
50   
51   /* Toplevel window to which the status window corresponds */
52   GtkWidget *toplevel;
53   
54   /* Signal connection ids; we connect to the toplevel */
55   gulong destroy_handler_id;
56   gulong configure_handler_id;
57 };
58
59 static void     gtk_im_context_xim_class_init         (GtkIMContextXIMClass  *class);
60 static void     gtk_im_context_xim_init               (GtkIMContextXIM       *im_context_xim);
61 static void     gtk_im_context_xim_finalize           (GObject               *obj);
62 static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *context,
63                                                        GdkWindow             *client_window);
64 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
65                                                        GdkEventKey           *key);
66 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
67 static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
68 static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
69 static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
70                                                        GdkRectangle             *area);
71 static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
72                                                        gboolean               use_preedit);
73 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
74                                                        gchar                **str,
75                                                        PangoAttrList        **attrs,
76                                                        gint                  *cursor_pos);
77
78 static void reinitialize_ic      (GtkIMContextXIM *context_xim,
79                                   gboolean         send_signal);
80 static void set_ic_client_window (GtkIMContextXIM *context_xim,
81                                   GdkWindow       *client_window,
82                                   gboolean         send_signal);
83
84 static void setup_styles (GtkXIMInfo *info);
85
86 static void status_window_show     (GtkIMContextXIM *context_xim);
87 static void status_window_hide     (GtkIMContextXIM *context_xim);
88 static void status_window_set_text (GtkIMContextXIM *context_xim,
89                                     const gchar     *text);
90
91 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
92 static GObjectClass *parent_class;
93
94 GType gtk_type_im_context_xim = 0;
95
96 GSList *open_ims = NULL;
97
98 /* List of status windows for different toplevels */
99 static GSList *status_windows = NULL;
100
101 void
102 gtk_im_context_xim_register_type (GTypeModule *type_module)
103 {
104   static const GTypeInfo im_context_xim_info =
105   {
106     sizeof (GtkIMContextXIMClass),
107     (GBaseInitFunc) NULL,
108     (GBaseFinalizeFunc) NULL,
109     (GClassInitFunc) gtk_im_context_xim_class_init,
110     NULL,           /* class_finalize */    
111     NULL,           /* class_data */
112     sizeof (GtkIMContextXIM),
113     0,
114     (GInstanceInitFunc) gtk_im_context_xim_init,
115   };
116
117   gtk_type_im_context_xim = 
118     g_type_module_register_type (type_module,
119                                  GTK_TYPE_IM_CONTEXT,
120                                  "GtkIMContextXIM",
121                                  &im_context_xim_info, 0);
122 }
123
124 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
125                       XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
126 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
127                       XIMStatusNothing | XIMStatusNone)
128 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
129                       XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
130
131 static XIMStyle 
132 choose_better_style (XIMStyle style1, XIMStyle style2) 
133 {
134   XIMStyle s1, s2, u; 
135   
136   if (style1 == 0) return style2;
137   if (style2 == 0) return style1;
138   if ((style1 & (PREEDIT_MASK | STATUS_MASK))
139         == (style2 & (PREEDIT_MASK | STATUS_MASK)))
140     return style1;
141
142   s1 = style1 & PREEDIT_MASK;
143   s2 = style2 & PREEDIT_MASK;
144   u = s1 | s2;
145   if (s1 != s2) {
146     if (u & XIMPreeditCallbacks)
147       return (s1 == XIMPreeditCallbacks) ? style1 : style2;
148     else if (u & XIMPreeditPosition)
149       return (s1 == XIMPreeditPosition) ? style1 :style2;
150     else if (u & XIMPreeditArea)
151       return (s1 == XIMPreeditArea) ? style1 : style2;
152     else if (u & XIMPreeditNothing)
153       return (s1 == XIMPreeditNothing) ? style1 : style2;
154   } else {
155     s1 = style1 & STATUS_MASK;
156     s2 = style2 & STATUS_MASK;
157     u = s1 | s2;
158     if (u & XIMStatusCallbacks)
159       return (s1 == XIMStatusCallbacks) ? style1 : style2;
160     else if (u & XIMStatusArea)
161       return (s1 == XIMStatusArea) ? style1 : style2;
162     else if (u & XIMStatusNothing)
163       return (s1 == XIMStatusNothing) ? style1 : style2;
164     else if (u & XIMStatusNone)
165       return (s1 == XIMStatusNone) ? style1 : style2;
166   }
167   return 0; /* Get rid of stupid warning */
168 }
169
170 static void
171 reinitialize_all_ics (GtkXIMInfo *info)
172 {
173   GSList *tmp_list;
174
175   for (tmp_list = info->ics; tmp_list; tmp_list = tmp_list->next)
176     reinitialize_ic (tmp_list->data, TRUE);
177 }
178
179 static void
180 status_style_change (GtkXIMInfo *info)
181 {
182   GtkIMStatusStyle status_style;
183   
184   g_object_get (info->settings,
185                 "gtk-im-status-style", &status_style,
186                 NULL);
187   if (status_style == GTK_IM_STATUS_CALLBACK)
188     info->status_style_setting = XIMStatusCallbacks;
189   else if (status_style == GTK_IM_STATUS_NOTHING)
190     info->status_style_setting = XIMStatusNothing;
191   else
192     return;
193
194   setup_styles (info);
195   
196   reinitialize_all_ics (info);
197 }
198
199 static void
200 preedit_style_change (GtkXIMInfo *info)
201 {
202   GtkIMPreeditStyle preedit_style;
203   g_object_get (info->settings,
204                 "gtk-im-preedit-style", &preedit_style,
205                 NULL);
206   if (preedit_style == GTK_IM_PREEDIT_CALLBACK)
207     info->preedit_style_setting = XIMPreeditCallbacks;
208   else if (preedit_style == GTK_IM_PREEDIT_NOTHING)
209     info->preedit_style_setting = XIMPreeditNothing;
210   else
211     return;
212
213   setup_styles (info);
214   
215   reinitialize_all_ics (info);
216 }
217
218 static void
219 setup_styles (GtkXIMInfo *info)
220 {
221   int i;
222   unsigned long settings_preference;
223   XIMStyles *xim_styles = info->xim_styles;
224
225   settings_preference = info->status_style_setting|info->preedit_style_setting;
226   if (xim_styles)
227     {
228       for (i = 0; i < xim_styles->count_styles; i++)
229         if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
230           {
231             if (settings_preference == xim_styles->supported_styles[i])
232               {
233                 info->style = settings_preference;
234                 break;
235               }
236             info->style = choose_better_style (info->style,
237                                                xim_styles->supported_styles[i]);
238           }
239     }
240 }
241
242 static void
243 setup_im (GtkXIMInfo *info)
244 {
245   XIMValuesList *ic_values = NULL;
246
247   if (info->im == NULL)
248     return;
249
250   XGetIMValues (info->im,
251                 XNQueryInputStyle, &info->xim_styles,
252                 XNQueryICValuesList, &ic_values,
253                 NULL);
254
255   info->settings = gtk_settings_get_for_screen (info->screen);
256
257   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
258                                      "gtk-im-preedit-style"))
259     gtk_settings_install_property (g_param_spec_enum ("gtk-im-preedit-style",
260                                                       _("IM Preedit style"),
261                                                       _("How to draw the input method preedit string"),
262                                                       GTK_TYPE_IM_PREEDIT_STYLE,
263                                                       GTK_IM_PREEDIT_CALLBACK,
264                                                       G_PARAM_READWRITE));
265
266   if (!g_object_class_find_property (G_OBJECT_GET_CLASS (info->settings),
267                                      "gtk-im-status-style"))
268     gtk_settings_install_property (g_param_spec_enum ("gtk-im-status-style",
269                                                       _("IM Status style"),
270                                                       _("How to draw the input method statusbar"),
271                                                       GTK_TYPE_IM_STATUS_STYLE,
272                                                       GTK_IM_STATUS_CALLBACK,
273                                                       G_PARAM_READWRITE));
274
275   info->status_set = g_signal_connect_swapped (info->settings,
276                                                "notify::gtk-im-status-style",
277                                                G_CALLBACK (status_style_change),
278                                                info);
279   info->preedit_set = g_signal_connect_swapped (info->settings,
280                                                 "notify::gtk-im-preedit-style",
281                                                 G_CALLBACK (preedit_style_change),
282                                                 info);
283
284   status_style_change (info);
285   preedit_style_change (info);
286
287 #if 0
288   if (ic_values)
289     {
290       for (i = 0; i < ic_values->count_values; i++)
291         g_print ("%s\n", ic_values->supported_values[i]);
292       for (i = 0; i < xim_styles->count_styles; i++)
293         g_print ("%#x\n", xim_styles->supported_styles[i]);
294     }
295 #endif
296
297   if (ic_values)
298     XFree (ic_values);
299 }
300
301 static void
302 xim_info_display_closed (GdkDisplay *display,
303                          gboolean    is_error,
304                          GtkXIMInfo *info)
305 {
306   GSList *ics, *tmp_list;
307
308   open_ims = g_slist_remove (open_ims, info);
309
310   ics = info->ics;
311   info->ics = NULL;
312
313   for (tmp_list = ics; tmp_list; tmp_list = tmp_list->next)
314     set_ic_client_window (tmp_list->data, NULL, TRUE);
315
316   g_slist_free (tmp_list);
317   
318   g_signal_handler_disconnect (info->settings, info->status_set);
319   g_signal_handler_disconnect (info->settings, info->preedit_set);
320   
321   XFree (info->xim_styles->supported_styles);
322   XFree (info->xim_styles);
323   g_free (info->locale);
324
325   if (info->im)
326     XCloseIM (info->im);
327
328   g_free (info);
329 }
330
331 static GtkXIMInfo *
332 get_im (GdkWindow *client_window,
333         const char *locale)
334 {
335   GSList *tmp_list;
336   GtkXIMInfo *info;
337   XIM im = NULL;
338   GdkScreen *screen = gdk_drawable_get_screen (client_window);
339   GdkDisplay *display = gdk_screen_get_display (screen);
340
341   tmp_list = open_ims;
342   while (tmp_list)
343     {
344       info = tmp_list->data;
345       if (info->screen == screen &&
346           strcmp (info->locale, locale) == 0)
347         return info;
348
349       tmp_list = tmp_list->next;
350     }
351
352   info = NULL;
353
354   if (XSupportsLocale ())
355     {
356       if (!XSetLocaleModifiers (""))
357         g_warning ("Unable to set locale modifiers with XSetLocaleModifiers()");
358       
359       im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
360       
361       if (!im)
362         g_warning ("Unable to open XIM input method, falling back to XLookupString()");
363
364       info = g_new (GtkXIMInfo, 1);
365       open_ims = g_slist_prepend (open_ims, info);
366
367       info->screen = screen;
368       info->locale = g_strdup (locale);
369       info->im = im;
370       info->xim_styles = NULL;
371       info->preedit_style_setting = 0;
372       info->status_style_setting = 0;
373       info->settings = NULL;
374       info->preedit_set = 0;
375       info->status_set = 0;
376       info->ics = NULL;
377
378       setup_im (info);
379
380       g_signal_connect (display, "closed",
381                         G_CALLBACK (xim_info_display_closed), info);
382     }
383
384   return info;
385 }
386
387 static void
388 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
389 {
390   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
391   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
392
393   parent_class = g_type_class_peek_parent (class);
394
395   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
396   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
397   im_context_class->reset = gtk_im_context_xim_reset;
398   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
399   im_context_class->focus_in = gtk_im_context_xim_focus_in;
400   im_context_class->focus_out = gtk_im_context_xim_focus_out;
401   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
402   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
403   gobject_class->finalize = gtk_im_context_xim_finalize;
404 }
405
406 static void
407 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
408 {
409   im_context_xim->use_preedit = TRUE;
410   im_context_xim->filter_key_release = FALSE;
411   im_context_xim->status_visible = FALSE;
412 }
413
414 static void
415 gtk_im_context_xim_finalize (GObject *obj)
416 {
417   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
418
419   set_ic_client_window (context_xim, NULL, FALSE);
420
421   g_free (context_xim->locale);
422   g_free (context_xim->mb_charset);
423 }
424
425 static void
426 reinitialize_ic (GtkIMContextXIM *context_xim,
427                  gboolean         send_signal)
428 {
429   if (context_xim->ic)
430     {
431       XDestroyIC (context_xim->ic);
432       status_window_hide (context_xim);
433       context_xim->ic = NULL;
434
435       if (context_xim->preedit_length)
436         {
437           context_xim->preedit_length = 0;
438           if (send_signal)
439             g_signal_emit_by_name (context_xim, "preedit_changed");
440         }
441     }
442 }
443
444 static void
445 set_ic_client_window (GtkIMContextXIM *context_xim,
446                       GdkWindow       *client_window,
447                       gboolean         send_signal)
448 {
449   reinitialize_ic (context_xim, send_signal);
450   if (context_xim->client_window)
451     {
452       context_xim->im_info->ics = g_slist_remove (context_xim->im_info->ics, context_xim);
453       context_xim->im_info = NULL;
454     }
455   
456   context_xim->client_window = client_window;
457
458   if (context_xim->client_window)
459     {
460       context_xim->im_info = get_im (context_xim->client_window, context_xim->locale);
461       context_xim->im_info->ics = g_slist_prepend (context_xim->im_info->ics, context_xim);
462     }
463 }
464
465 static void
466 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
467                                       GdkWindow             *client_window)
468 {
469   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
470
471   set_ic_client_window (context_xim, client_window, TRUE);
472 }
473
474 GtkIMContext *
475 gtk_im_context_xim_new (void)
476 {
477   GtkIMContextXIM *result;
478   const gchar *charset;
479
480   result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
481
482   result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
483   
484   g_get_charset (&charset);
485   result->mb_charset = g_strdup (charset);
486
487   return GTK_IM_CONTEXT (result);
488 }
489
490 static char *
491 mb_to_utf8 (GtkIMContextXIM *context_xim,
492             const char      *str)
493 {
494   GError *error = NULL;
495   gchar *result;
496
497   if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
498     result = g_strdup (str);
499   else
500     {
501       result = g_convert (str, -1,
502                           "UTF-8", context_xim->mb_charset,
503                           NULL, NULL, &error);
504       if (!result)
505         {
506           g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
507           g_error_free (error);
508         }
509     }
510   
511   return result;
512 }
513
514 static gboolean
515 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
516                                     GdkEventKey  *event)
517 {
518   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
519   XIC ic = gtk_im_context_xim_get_ic (context_xim);
520   gchar static_buffer[256];
521   gchar *buffer = static_buffer;
522   gint buffer_size = sizeof(static_buffer) - 1;
523   gint num_bytes = 0;
524   KeySym keysym;
525   Status status;
526   gboolean result = FALSE;
527   GdkWindow *root_window = gdk_screen_get_root_window (gdk_drawable_get_screen (event->window));
528
529   XKeyPressedEvent xevent;
530
531   if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
532     return FALSE;
533
534   xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
535   xevent.serial = 0;            /* hope it doesn't matter */
536   xevent.send_event = event->send_event;
537   xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
538   xevent.window = GDK_DRAWABLE_XID (event->window);
539   xevent.root = GDK_DRAWABLE_XID (root_window);
540   xevent.subwindow = xevent.window;
541   xevent.time = event->time;
542   xevent.x = xevent.x_root = 0;
543   xevent.y = xevent.y_root = 0;
544   xevent.state = event->state;
545   xevent.keycode = event->hardware_keycode;
546   xevent.same_screen = True;
547   
548   if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
549     return TRUE;
550   
551  again:
552   if (ic)
553     num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
554   else
555     {
556       num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
557       status = XLookupBoth;
558     }
559
560   if (status == XBufferOverflow)
561     {
562       buffer_size = num_bytes;
563       buffer = g_malloc (num_bytes + 1);
564       goto again;
565     }
566
567   /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
568    * here ... do input methods actually change the keysym? we can't really
569    * feed it back to accelerator processing at this point...
570    */
571   if (status == XLookupChars || status == XLookupBoth)
572     {
573       char *result_utf8;
574
575       buffer[num_bytes] = '\0';
576
577       result_utf8 = mb_to_utf8 (context_xim, buffer);
578       if (result_utf8)
579         {
580           if ((guchar)result_utf8[0] >= 0x20 &&
581               result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
582                                        * control characters into strings
583                                        */
584             {
585               g_signal_emit_by_name (context, "commit", result_utf8);
586               result = TRUE;
587             }
588           
589           g_free (result_utf8);
590         }
591     }
592
593   return result;
594 }
595
596 static void
597 gtk_im_context_xim_focus_in (GtkIMContext *context)
598 {
599   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
600   XIC ic = gtk_im_context_xim_get_ic (context_xim);
601
602   if (!ic)
603     return;
604
605   XSetICFocus (ic);
606
607   status_window_show (context_xim);
608
609   return;
610 }
611
612 static void
613 gtk_im_context_xim_focus_out (GtkIMContext *context)
614 {
615   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
616   XIC ic = gtk_im_context_xim_get_ic (context_xim);
617
618   if (!ic)
619     return;
620
621   XUnsetICFocus (ic);
622
623   status_window_hide (context_xim);
624
625   return;
626 }
627
628 static void
629 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
630                                         GdkRectangle *area)
631 {
632   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
633   XIC ic = gtk_im_context_xim_get_ic (context_xim);
634
635   XVaNestedList preedit_attr;
636   XPoint          spot;
637
638   if (!ic)
639     return;
640
641   spot.x = area->x;
642   spot.y = area->y;
643
644   preedit_attr = XVaCreateNestedList (0,
645                                       XNSpotLocation, &spot,
646                                       0);
647   XSetICValues (ic,
648                 XNPreeditAttributes, preedit_attr,
649                 NULL);
650   XFree(preedit_attr);
651
652   return;
653 }
654
655 static void
656 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
657                                     gboolean      use_preedit)
658 {
659   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
660
661   use_preedit = use_preedit != FALSE;
662
663   if (context_xim->use_preedit != use_preedit)
664     {
665       context_xim->use_preedit = use_preedit;
666       reinitialize_ic (context_xim, TRUE);
667     }
668
669   return;
670 }
671
672 static void
673 gtk_im_context_xim_reset (GtkIMContext *context)
674 {
675   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
676   XIC ic = gtk_im_context_xim_get_ic (context_xim);
677   gchar *result;
678
679   /* restore conversion state after resetting ic later */
680   XIMPreeditState preedit_state = XIMPreeditUnKnown;
681   XVaNestedList preedit_attr;
682   gboolean have_preedit_state = FALSE;
683
684   if (!ic)
685     return;
686   
687
688   if (context_xim->preedit_length == 0)
689     return;
690
691   preedit_attr = XVaCreateNestedList(0,
692                                      XNPreeditState, &preedit_state,
693                                      0);
694   if (!XGetICValues(ic,
695                     XNPreeditAttributes, preedit_attr,
696                     NULL))
697     have_preedit_state = TRUE;
698
699   XFree(preedit_attr);
700
701   result = XmbResetIC (ic);
702
703   preedit_attr = XVaCreateNestedList(0,
704                                      XNPreeditState, preedit_state,
705                                      0);
706   if (have_preedit_state)
707     XSetICValues(ic,
708                  XNPreeditAttributes, preedit_attr,
709                  NULL);
710
711   XFree(preedit_attr);
712
713   if (result)
714     {
715       char *result_utf8 = mb_to_utf8 (context_xim, result);
716       if (result_utf8)
717         {
718           g_signal_emit_by_name (context, "commit", result_utf8);
719           g_free (result_utf8);
720         }
721     }
722
723   if (context_xim->preedit_length)
724     {
725       context_xim->preedit_length = 0;
726       g_signal_emit_by_name (context, "preedit_changed");
727     }
728
729   XFree (result);
730 }
731
732 /* Mask of feedback bits that we render
733  */
734 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
735
736 static void
737 add_feedback_attr (PangoAttrList *attrs,
738                    const gchar   *str,
739                    XIMFeedback    feedback,
740                    gint           start_pos,
741                    gint           end_pos)
742 {
743   PangoAttribute *attr;
744   
745   gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
746   gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
747
748   if (feedback & XIMUnderline)
749     {
750       attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
751       attr->start_index = start_index;
752       attr->end_index = end_index;
753
754       pango_attr_list_change (attrs, attr);
755     }
756
757   if (feedback & XIMReverse)
758     {
759       attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
760       attr->start_index = start_index;
761       attr->end_index = end_index;
762
763       pango_attr_list_change (attrs, attr);
764
765       attr = pango_attr_background_new (0, 0, 0);
766       attr->start_index = start_index;
767       attr->end_index = end_index;
768
769       pango_attr_list_change (attrs, attr);
770     }
771
772   if (feedback & ~FEEDBACK_MASK)
773     g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
774 }
775
776 static void     
777 gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
778                                        gchar         **str,
779                                        PangoAttrList **attrs,
780                                        gint           *cursor_pos)
781 {
782   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
783   gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
784
785   if (attrs)
786     {
787       int i;
788       XIMFeedback last_feedback = 0;
789       gint start = -1;
790       
791       *attrs = pango_attr_list_new ();
792
793       for (i = 0; i < context_xim->preedit_length; i++)
794         {
795           XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
796           if (new_feedback != last_feedback)
797             {
798               if (start >= 0)
799                 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
800               
801               last_feedback = new_feedback;
802               start = i;
803             }
804         }
805
806       if (start >= 0)
807         add_feedback_attr (*attrs, utf8, last_feedback, start, i);
808     }
809
810   if (str)
811     *str = utf8;
812   else
813     g_free (utf8);
814
815   if (cursor_pos)
816     *cursor_pos = context_xim->preedit_cursor;
817 }
818
819 static void
820 preedit_start_callback (XIC      xic,
821                         XPointer client_data,
822                         XPointer call_data)
823 {
824   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
825   
826   g_signal_emit_by_name (context, "preedit_start");
827 }                    
828
829 static void
830 preedit_done_callback (XIC      xic,
831                      XPointer client_data,
832                      XPointer call_data)
833 {
834   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
835   
836   g_signal_emit_by_name (context, "preedit_end");  
837 }                    
838
839 static gint
840 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
841 {
842   gint text_length = 0;
843   GError *error = NULL;
844   gchar *result = NULL;
845
846   if (xim_text && xim_text->string.multi_byte)
847     {
848       if (xim_text->encoding_is_wchar)
849         {
850           g_warning ("Wide character return from Xlib not currently supported");
851           *text = NULL;
852           return 0;
853         }
854
855       if (strcmp (context->mb_charset, "UTF-8") == 0)
856         result = g_strdup (xim_text->string.multi_byte);
857       else
858         result = g_convert (xim_text->string.multi_byte,
859                             -1,
860                             "UTF-8",
861                             context->mb_charset,
862                             NULL, NULL, &error);
863       
864       if (result)
865         {
866           text_length = g_utf8_strlen (result, -1);
867           
868           if (text_length != xim_text->length)
869             {
870               g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
871             }
872         }
873       else
874         {
875           g_warning ("Error converting text from IM to UCS-4: %s", error->message);
876           g_error_free (error);
877
878           *text = NULL;
879           return 0;
880         }
881
882       *text = result;
883       return text_length;
884     }
885   else
886     {
887       *text = NULL;
888       return 0;
889     }
890 }
891
892 static void
893 preedit_draw_callback (XIC                           xic, 
894                        XPointer                      client_data,
895                        XIMPreeditDrawCallbackStruct *call_data)
896 {
897   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
898
899   XIMText *new_xim_text = call_data->text;
900   gint new_text_length;
901   gunichar *new_text = NULL;
902   gint i;
903   gint diff;
904   gint new_length;
905   gchar *tmp;
906   
907   gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
908   gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
909
910   context->preedit_cursor = call_data->caret;
911   
912   if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
913     g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
914                call_data->chg_first, call_data->chg_length, context->preedit_length);
915
916   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
917   if (tmp)
918     {
919       new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
920       g_free (tmp);
921     }
922   
923   diff = new_text_length - chg_length;
924   new_length = context->preedit_length + diff;
925
926   if (new_length > context->preedit_size)
927     {
928       context->preedit_size = new_length;
929       context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
930       context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
931     }
932
933   if (diff < 0)
934     {
935       for (i = chg_first + chg_length ; i < context->preedit_length; i++)
936         {
937           context->preedit_chars[i + diff] = context->preedit_chars[i];
938           context->feedbacks[i + diff] = context->feedbacks[i];
939         }
940     }
941   else
942     {
943       for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
944         {
945           context->preedit_chars[i + diff] = context->preedit_chars[i];
946           context->feedbacks[i + diff] = context->feedbacks[i];
947         }
948     }
949
950   for (i = 0; i < new_text_length; i++)
951     {
952       context->preedit_chars[chg_first + i] = new_text[i];
953       context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
954     }
955
956   context->preedit_length += diff;
957
958   if (new_text)
959     g_free (new_text);
960
961   g_signal_emit_by_name (context, "preedit_changed");
962 }
963     
964
965 static void
966 preedit_caret_callback (XIC                            xic,
967                         XPointer                       client_data,
968                         XIMPreeditCaretCallbackStruct *call_data)
969 {
970   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
971   
972   if (call_data->direction == XIMAbsolutePosition)
973     {
974       context->preedit_cursor = call_data->position;
975       g_signal_emit_by_name (context, "preedit_changed");
976     }
977   else
978     {
979       g_warning ("Caret movement command: %d %d %d not supported",
980                  call_data->position, call_data->direction, call_data->style);
981     }
982 }            
983
984 static void
985 status_start_callback (XIC      xic,
986                        XPointer client_data,
987                        XPointer call_data)
988 {
989   return;
990
991
992 static void
993 status_done_callback (XIC      xic,
994                       XPointer client_data,
995                       XPointer call_data)
996 {
997   return;
998 }
999
1000 static void
1001 status_draw_callback (XIC      xic,
1002                       XPointer client_data,
1003                       XIMStatusDrawCallbackStruct *call_data)
1004 {
1005   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
1006
1007   if (context->status_visible == FALSE)
1008     return;
1009
1010   if (call_data->type == XIMTextType)
1011     {
1012       gchar *text;
1013       xim_text_to_utf8 (context, call_data->data.text, &text);
1014
1015       if (text)
1016         status_window_set_text (context, text);
1017       else
1018         status_window_set_text (context, "");
1019     }
1020   else                          /* bitmap */
1021     {
1022       g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
1023     }
1024 }
1025
1026 static XVaNestedList
1027 set_preedit_callback (GtkIMContextXIM *context_xim)
1028 {
1029   context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
1030   context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
1031   context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
1032   context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
1033   context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
1034   context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
1035   context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
1036   context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
1037   return XVaCreateNestedList (0,
1038                               XNPreeditStartCallback, &context_xim->preedit_start_callback,
1039                               XNPreeditDoneCallback, &context_xim->preedit_done_callback,
1040                               XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
1041                               XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
1042                               NULL);
1043 }
1044
1045 static XVaNestedList
1046 set_status_callback (GtkIMContextXIM *context_xim)
1047 {
1048   context_xim->status_start_callback.client_data = (XPointer)context_xim;
1049   context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
1050   context_xim->status_done_callback.client_data = (XPointer)context_xim;
1051   context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
1052   context_xim->status_draw_callback.client_data = (XPointer)context_xim;
1053   context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
1054           
1055   return XVaCreateNestedList (0,
1056                               XNStatusStartCallback, &context_xim->status_start_callback,
1057                               XNStatusDoneCallback, &context_xim->status_done_callback,
1058                               XNStatusDrawCallback, &context_xim->status_draw_callback,
1059                               NULL);
1060 }
1061
1062 static XIC
1063 get_ic_real (GtkIMContextXIM *context_xim)
1064 {
1065   XIC xic = 0;
1066   const char *name1 = NULL;
1067   XVaNestedList list1 = NULL;
1068   const char *name2 = NULL;
1069   XVaNestedList list2 = NULL;
1070   XIMStyle im_style = 0;
1071
1072   if (context_xim->im_info->im == NULL)
1073     return (XIC)0;
1074
1075   if (context_xim->use_preedit &&
1076       (context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
1077     {
1078       im_style |= XIMPreeditCallbacks;
1079       name1 = XNPreeditAttributes;
1080       list1 = set_preedit_callback (context_xim);
1081     }
1082   else
1083     im_style |= XIMPreeditNothing;
1084
1085   if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
1086     {
1087       im_style |= XIMStatusCallbacks;
1088       if (name1 == NULL)
1089         {
1090           name1 = XNStatusAttributes;
1091           list1 = set_status_callback (context_xim);
1092         }
1093       else
1094         {
1095           name2 = XNStatusAttributes;
1096           list2 = set_status_callback (context_xim);
1097         }
1098     }
1099   else
1100     im_style |= XIMStatusNothing;
1101
1102   xic = XCreateIC (context_xim->im_info->im,
1103                    XNInputStyle, im_style,
1104                    XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
1105                    name1, list1,
1106                    name2, list2,
1107                    NULL);
1108   if (list1)
1109     XFree (list1);
1110   if (list2)
1111     XFree (list2);
1112
1113   if (xic)
1114     {
1115       /* Don't filter key released events with XFilterEvents unless
1116        * input methods ask for. This is a workaround for Solaris input
1117        * method bug in C and European locales. It doubles each key
1118        * stroke if both key pressed and released events are filtered.
1119        * (bugzilla #81759)
1120        */
1121       gulong mask = 0;
1122       XGetICValues (xic,
1123                     XNFilterEvents, &mask,
1124                     NULL);
1125       context_xim->filter_key_release = (mask & KeyReleaseMask);
1126     }
1127
1128   return xic;
1129 }
1130
1131 static XIC
1132 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
1133 {
1134   if (!context_xim->ic && context_xim->im_info)
1135     context_xim->ic = get_ic_real (context_xim);
1136   
1137   return context_xim->ic;
1138 }
1139
1140 /**************************
1141  *                        *
1142  * Status Window handling *
1143  *                        *
1144  **************************/
1145
1146 static gboolean
1147 status_window_expose_event (GtkWidget      *widget,
1148                             GdkEventExpose *event)
1149 {
1150   gdk_draw_rectangle (widget->window,
1151                       widget->style->base_gc [GTK_STATE_NORMAL],
1152                       TRUE,
1153                       0, 0,
1154                       widget->allocation.width, widget->allocation.height);
1155   gdk_draw_rectangle (widget->window,
1156                       widget->style->text_gc [GTK_STATE_NORMAL],
1157                       FALSE,
1158                       0, 0,
1159                       widget->allocation.width - 1, widget->allocation.height - 1);
1160
1161   return FALSE;
1162 }
1163
1164 static void
1165 status_window_style_set (GtkWidget *toplevel,
1166                          GtkStyle  *previous_style,
1167                          GtkWidget *label)
1168 {
1169   gint i;
1170   
1171   for (i = 0; i < 5; i++)
1172     gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
1173 }
1174
1175 /* Frees a status window and removes its link from the status_windows list */
1176 static void
1177 status_window_free (StatusWindow *status_window)
1178 {
1179   status_windows = g_slist_remove (status_windows, status_window);
1180  
1181   g_signal_handler_disconnect (status_window->toplevel, status_window->destroy_handler_id);
1182   g_signal_handler_disconnect (status_window->toplevel, status_window->configure_handler_id);
1183   gtk_widget_destroy (status_window->window);
1184   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1185  
1186   g_free (status_window);
1187 }
1188
1189 static gboolean
1190 status_window_configure (GtkWidget         *toplevel,
1191                          GdkEventConfigure *event,
1192                          StatusWindow      *status_window)
1193 {
1194   GdkRectangle rect;
1195   GtkRequisition requisition;
1196   gint y;
1197   gint height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
1198   
1199   gdk_window_get_frame_extents (toplevel->window, &rect);
1200   gtk_widget_size_request (status_window->window, &requisition);
1201
1202   if (rect.y + rect.height + requisition.height < height)
1203     y = rect.y + rect.height;
1204   else
1205     y = height - requisition.height;
1206   
1207   gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1208
1209   return FALSE;
1210 }
1211
1212 static GtkWidget *
1213 status_window_get (GtkIMContextXIM *context_xim,
1214                    gboolean         create)
1215 {
1216   GdkWindow *toplevel_gdk;
1217   GtkWidget *toplevel;
1218   GtkWidget *window;
1219   StatusWindow *status_window;
1220   GtkWidget *status_label;
1221   GdkScreen *screen;
1222   GdkWindow *root_window;
1223   
1224   if (!context_xim->client_window)
1225     return NULL;
1226
1227   toplevel_gdk = context_xim->client_window;
1228   screen = gdk_drawable_get_screen (toplevel_gdk);
1229   root_window = gdk_screen_get_root_window (screen);
1230   
1231   while (TRUE)
1232     {
1233       GdkWindow *parent = gdk_window_get_parent (toplevel_gdk);
1234       if (parent == root_window)
1235         break;
1236       else
1237         toplevel_gdk = parent;
1238     }
1239
1240   gdk_window_get_user_data (toplevel_gdk, (gpointer *)&toplevel);
1241   if (!toplevel)
1242     return NULL;
1243
1244   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1245   if (status_window)
1246     return status_window->window;
1247   else if (!create)
1248     return NULL;
1249
1250   status_window = g_new (StatusWindow, 1);
1251   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1252   status_window->toplevel = toplevel;
1253
1254   status_windows = g_slist_prepend (status_windows, status_window);
1255
1256   window = status_window->window;
1257
1258   gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
1259   gtk_widget_set_app_paintable (window, TRUE);
1260
1261   status_label = gtk_label_new ("");
1262   gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
1263   gtk_widget_show (status_label);
1264   
1265   gtk_container_add (GTK_CONTAINER (window), status_label);
1266
1267   status_window->destroy_handler_id = g_signal_connect_swapped (toplevel, "destroy",
1268                                                                 G_CALLBACK (status_window_free),
1269                                                                 status_window);
1270   status_window->configure_handler_id = g_signal_connect (toplevel, "configure_event",
1271                                                           G_CALLBACK (status_window_configure),
1272                                                           status_window);
1273
1274   status_window_configure (toplevel, NULL, status_window);
1275
1276   g_signal_connect (window, "style_set",
1277                     G_CALLBACK (status_window_style_set), status_label);
1278   g_signal_connect (window, "expose_event",
1279                     G_CALLBACK (status_window_expose_event), NULL);
1280
1281   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1282
1283   return window;
1284 }
1285
1286 static gboolean
1287 status_window_has_text (GtkWidget *status_window)
1288 {
1289   GtkWidget *label = GTK_BIN (status_window)->child;
1290   const gchar *text = gtk_label_get_text (GTK_LABEL (label));
1291
1292   return text[0] != '\0';
1293 }
1294
1295 static void
1296 status_window_show (GtkIMContextXIM *context_xim)
1297 {
1298   GtkWidget *status_window = status_window_get (context_xim, TRUE);
1299
1300   context_xim->status_visible = TRUE;
1301 }
1302
1303 static void
1304 status_window_hide (GtkIMContextXIM *context_xim)
1305 {
1306   GtkWidget *status_window = status_window_get (context_xim, FALSE);
1307
1308   context_xim->status_visible = FALSE;
1309   
1310   status_window_set_text (context_xim, "");
1311 }
1312
1313 static void
1314 status_window_set_text (GtkIMContextXIM *context_xim,
1315                         const gchar     *text)
1316 {
1317   GtkWidget *status_window = status_window_get (context_xim, TRUE);
1318
1319   if (status_window)
1320     {
1321       GtkWidget *label = GTK_BIN (status_window)->child;
1322       gtk_label_set_text (GTK_LABEL (label), text);
1323       
1324       if (context_xim->status_visible && status_window_has_text (status_window))
1325         gtk_widget_show (status_window);
1326       else
1327         gtk_widget_hide (status_window);
1328     }
1329 }
1330
1331 /**
1332  * gtk_im_context_xim_shutdown:
1333  * 
1334  * Destroys all the status windows that are kept by the XIM contexts.  This
1335  * function should only be called by the XIM module exit routine.
1336  **/
1337 void
1338 gtk_im_context_xim_shutdown (void)
1339 {
1340   while (status_windows)
1341     status_window_free (status_windows->data);
1342 }