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