]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextxim.c
modify a warning when XSetLocaleModifiers() fails, and add a warning when
[~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 ("Unable to set locale modifiers with XSetLocaleModifiers()");
217       
218       im = XOpenIM (GDK_DISPLAY_XDISPLAY (display), NULL, NULL, NULL);
219       
220       if (!im)
221         g_warning ("Unable to open XIM input method, falling back to XLookupString()");
222
223       if (im)
224         {
225           info = g_new (GtkXIMInfo, 1);
226           open_ims = g_slist_prepend (open_ims, im);
227
228           info->display = display;
229           info->locale = g_strdup (locale);
230           info->im = im;
231
232           setup_im (info);
233         }
234     }
235
236   return info;
237 }
238
239 static void
240 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
241 {
242   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
243   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
244
245   parent_class = g_type_class_peek_parent (class);
246
247   im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
248   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
249   im_context_class->reset = gtk_im_context_xim_reset;
250   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
251   im_context_class->focus_in = gtk_im_context_xim_focus_in;
252   im_context_class->focus_out = gtk_im_context_xim_focus_out;
253   im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
254   im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
255   gobject_class->finalize = gtk_im_context_xim_finalize;
256 }
257
258 static void
259 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
260 {
261   im_context_xim->use_preedit = TRUE;
262   im_context_xim->filter_key_release = FALSE;
263 }
264
265 static void
266 gtk_im_context_xim_finalize (GObject *obj)
267 {
268   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
269
270   if (context_xim->ic)
271     {
272       XDestroyIC (context_xim->ic);
273       context_xim->ic = NULL;
274     }
275  
276   g_free (context_xim->locale);
277   g_free (context_xim->mb_charset);
278 }
279
280 static void
281 reinitialize_ic (GtkIMContextXIM *context_xim)
282 {
283   if (context_xim->ic)
284     {
285       XDestroyIC (context_xim->ic);
286       context_xim->ic = NULL;
287
288       if (context_xim->preedit_length)
289         {
290           context_xim->preedit_length = 0;
291           g_signal_emit_by_name (context_xim, "preedit_changed");
292         }
293     }
294 }
295
296 static void
297 gtk_im_context_xim_set_client_window (GtkIMContext          *context,
298                                       GdkWindow             *client_window)
299 {
300   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
301
302   reinitialize_ic (context_xim);
303   context_xim->client_window = client_window;
304
305   if (context_xim->client_window)
306     context_xim->im_info = get_im (gdk_drawable_get_display (context_xim->client_window), context_xim->locale);
307   else
308     context_xim->im_info = NULL;
309 }
310
311 GtkIMContext *
312 gtk_im_context_xim_new (void)
313 {
314   GtkIMContextXIM *result;
315   const gchar *charset;
316
317   result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
318
319   result->locale = g_strdup (setlocale (LC_CTYPE, NULL));
320   
321   g_get_charset (&charset);
322   result->mb_charset = g_strdup (charset);
323
324   return GTK_IM_CONTEXT (result);
325 }
326
327 static char *
328 mb_to_utf8 (GtkIMContextXIM *context_xim,
329             const char      *str)
330 {
331   GError *error = NULL;
332   gchar *result;
333
334   if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
335     result = g_strdup (str);
336   else
337     {
338       result = g_convert (str, -1,
339                           "UTF-8", context_xim->mb_charset,
340                           NULL, NULL, &error);
341       if (!result)
342         {
343           g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
344           g_error_free (error);
345         }
346     }
347   
348   return result;
349 }
350
351 static gboolean
352 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
353                                     GdkEventKey  *event)
354 {
355   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
356   XIC ic = gtk_im_context_xim_get_ic (context_xim);
357   gchar static_buffer[256];
358   gchar *buffer = static_buffer;
359   gint buffer_size = sizeof(static_buffer) - 1;
360   gint num_bytes = 0;
361   KeySym keysym;
362   Status status;
363   gboolean result = FALSE;
364   GdkWindow *root_window = gdk_screen_get_root_window (gdk_drawable_get_screen (event->window));
365
366   XKeyPressedEvent xevent;
367
368   if (event->type == GDK_KEY_RELEASE && !context_xim->filter_key_release)
369     return FALSE;
370
371   xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
372   xevent.serial = 0;            /* hope it doesn't matter */
373   xevent.send_event = event->send_event;
374   xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
375   xevent.window = GDK_DRAWABLE_XID (event->window);
376   xevent.root = GDK_DRAWABLE_XID (root_window);
377   xevent.subwindow = xevent.window;
378   xevent.time = event->time;
379   xevent.x = xevent.x_root = 0;
380   xevent.y = xevent.y_root = 0;
381   xevent.state = event->state;
382   xevent.keycode = event->hardware_keycode;
383   xevent.same_screen = True;
384   
385   if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
386     return TRUE;
387   
388  again:
389   if (ic)
390     num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
391   else
392     {
393       num_bytes = XLookupString (&xevent, buffer, buffer_size, &keysym, NULL);
394       status = XLookupBoth;
395     }
396
397   if (status == XBufferOverflow)
398     {
399       buffer_size = num_bytes;
400       buffer = g_malloc (num_bytes + 1);
401       goto again;
402     }
403
404   /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
405    * here ... do input methods actually change the keysym? we can't really
406    * feed it back to accelerator processing at this point...
407    */
408   if (status == XLookupChars || status == XLookupBoth)
409     {
410       char *result_utf8;
411
412       buffer[num_bytes] = '\0';
413
414       result_utf8 = mb_to_utf8 (context_xim, buffer);
415       if (result_utf8)
416         {
417           if ((guchar)result_utf8[0] >= 0x20 &&
418               result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
419                                        * control characters into strings
420                                        */
421             {
422               g_signal_emit_by_name (context, "commit", result_utf8);
423               result = TRUE;
424             }
425           
426           g_free (result_utf8);
427         }
428     }
429
430   return result;
431 }
432
433 static void
434 gtk_im_context_xim_focus_in (GtkIMContext *context)
435 {
436   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
437   XIC ic = gtk_im_context_xim_get_ic (context_xim);
438
439   if (!ic)
440     return;
441
442   XSetICFocus (ic);
443
444   status_window_show (context_xim);
445
446   return;
447 }
448
449 static void
450 gtk_im_context_xim_focus_out (GtkIMContext *context)
451 {
452   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
453   XIC ic = gtk_im_context_xim_get_ic (context_xim);
454
455   if (!ic)
456     return;
457
458   XUnsetICFocus (ic);
459
460   status_window_hide (context_xim);
461
462   return;
463 }
464
465 static void
466 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
467                                         GdkRectangle *area)
468 {
469   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
470   XIC ic = gtk_im_context_xim_get_ic (context_xim);
471
472   XVaNestedList preedit_attr;
473   XPoint          spot;
474
475   if (!ic)
476     return;
477
478   spot.x = area->x;
479   spot.y = area->y;
480
481   preedit_attr = XVaCreateNestedList (0,
482                                       XNSpotLocation, &spot,
483                                       0);
484   XSetICValues (ic,
485                 XNPreeditAttributes, preedit_attr,
486                 NULL);
487   XFree(preedit_attr);
488
489   return;
490 }
491
492 static void
493 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
494                                     gboolean      use_preedit)
495 {
496   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
497
498   use_preedit = use_preedit != FALSE;
499
500   if (context_xim->use_preedit != use_preedit)
501     {
502       context_xim->use_preedit = use_preedit;
503       reinitialize_ic (context_xim);
504     }
505
506   return;
507 }
508
509 static void
510 gtk_im_context_xim_reset (GtkIMContext *context)
511 {
512   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
513   XIC ic = gtk_im_context_xim_get_ic (context_xim);
514   gchar *result;
515
516   /* restore conversion state after resetting ic later */
517   XIMPreeditState preedit_state = XIMPreeditUnKnown;
518   XVaNestedList preedit_attr;
519   gboolean have_preedit_state = FALSE;
520
521   if (!ic)
522     return;
523   
524
525   if (context_xim->preedit_length == 0)
526     return;
527
528   preedit_attr = XVaCreateNestedList(0,
529                                      XNPreeditState, &preedit_state,
530                                      0);
531   if (!XGetICValues(ic,
532                     XNPreeditAttributes, preedit_attr,
533                     NULL))
534     have_preedit_state = TRUE;
535
536   XFree(preedit_attr);
537
538   result = XmbResetIC (ic);
539
540   preedit_attr = XVaCreateNestedList(0,
541                                      XNPreeditState, preedit_state,
542                                      0);
543   if (have_preedit_state)
544     XSetICValues(ic,
545                  XNPreeditAttributes, preedit_attr,
546                  NULL);
547
548   XFree(preedit_attr);
549
550   if (result)
551     {
552       char *result_utf8 = mb_to_utf8 (context_xim, result);
553       if (result_utf8)
554         {
555           g_signal_emit_by_name (context, "commit", result_utf8);
556           g_free (result_utf8);
557         }
558     }
559
560   if (context_xim->preedit_length)
561     {
562       context_xim->preedit_length = 0;
563       g_signal_emit_by_name (context, "preedit_changed");
564     }
565
566   XFree (result);
567 }
568
569 /* Mask of feedback bits that we render
570  */
571 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
572
573 static void
574 add_feedback_attr (PangoAttrList *attrs,
575                    const gchar   *str,
576                    XIMFeedback    feedback,
577                    gint           start_pos,
578                    gint           end_pos)
579 {
580   PangoAttribute *attr;
581   
582   gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
583   gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
584
585   if (feedback & XIMUnderline)
586     {
587       attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
588       attr->start_index = start_index;
589       attr->end_index = end_index;
590
591       pango_attr_list_change (attrs, attr);
592     }
593
594   if (feedback & XIMReverse)
595     {
596       attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
597       attr->start_index = start_index;
598       attr->end_index = end_index;
599
600       pango_attr_list_change (attrs, attr);
601
602       attr = pango_attr_background_new (0, 0, 0);
603       attr->start_index = start_index;
604       attr->end_index = end_index;
605
606       pango_attr_list_change (attrs, attr);
607     }
608
609   if (feedback & ~FEEDBACK_MASK)
610     g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
611 }
612
613 static void     
614 gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
615                                        gchar         **str,
616                                        PangoAttrList **attrs,
617                                        gint           *cursor_pos)
618 {
619   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
620   gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
621
622   if (attrs)
623     {
624       int i;
625       XIMFeedback last_feedback = 0;
626       gint start = -1;
627       
628       *attrs = pango_attr_list_new ();
629
630       for (i = 0; i < context_xim->preedit_length; i++)
631         {
632           XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
633           if (new_feedback != last_feedback)
634             {
635               if (start >= 0)
636                 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
637               
638               last_feedback = new_feedback;
639               start = i;
640             }
641         }
642
643       if (start >= 0)
644         add_feedback_attr (*attrs, utf8, last_feedback, start, i);
645     }
646
647   if (str)
648     *str = utf8;
649   else
650     g_free (utf8);
651
652   if (cursor_pos)
653     *cursor_pos = context_xim->preedit_cursor;
654 }
655
656 static void
657 preedit_start_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_start");
664 }                    
665
666 static void
667 preedit_done_callback (XIC      xic,
668                      XPointer client_data,
669                      XPointer call_data)
670 {
671   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
672   
673   g_signal_emit_by_name (context, "preedit_end");  
674 }                    
675
676 static gint
677 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
678 {
679   gint text_length = 0;
680   GError *error = NULL;
681   gchar *result = NULL;
682
683   if (xim_text && xim_text->string.multi_byte)
684     {
685       if (xim_text->encoding_is_wchar)
686         {
687           g_warning ("Wide character return from Xlib not currently supported");
688           *text = NULL;
689           return 0;
690         }
691
692       if (strcmp (context->mb_charset, "UTF-8") == 0)
693         result = g_strdup (xim_text->string.multi_byte);
694       else
695         result = g_convert (xim_text->string.multi_byte,
696                             -1,
697                             "UTF-8",
698                             context->mb_charset,
699                             NULL, NULL, &error);
700       
701       if (result)
702         {
703           text_length = g_utf8_strlen (result, -1);
704           
705           if (text_length != xim_text->length)
706             {
707               g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
708             }
709         }
710       else
711         {
712           g_warning ("Error converting text from IM to UCS-4: %s", error->message);
713           g_error_free (error);
714
715           *text = NULL;
716           return 0;
717         }
718
719       *text = result;
720       return text_length;
721     }
722   else
723     {
724       *text = NULL;
725       return 0;
726     }
727 }
728
729 static void
730 preedit_draw_callback (XIC                           xic, 
731                        XPointer                      client_data,
732                        XIMPreeditDrawCallbackStruct *call_data)
733 {
734   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
735
736   XIMText *new_xim_text = call_data->text;
737   gint new_text_length;
738   gunichar *new_text = NULL;
739   gint i;
740   gint diff;
741   gint new_length;
742   gchar *tmp;
743   
744   gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
745   gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
746
747   context->preedit_cursor = call_data->caret;
748   
749   if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
750     g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
751                call_data->chg_first, call_data->chg_length, context->preedit_length);
752
753   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
754   if (tmp)
755     {
756       new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
757       g_free (tmp);
758     }
759   
760   diff = new_text_length - chg_length;
761   new_length = context->preedit_length + diff;
762
763   if (new_length > context->preedit_size)
764     {
765       context->preedit_size = new_length;
766       context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
767       context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
768     }
769
770   if (diff < 0)
771     {
772       for (i = chg_first + chg_length ; i < context->preedit_length; i++)
773         {
774           context->preedit_chars[i + diff] = context->preedit_chars[i];
775           context->feedbacks[i + diff] = context->feedbacks[i];
776         }
777     }
778   else
779     {
780       for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
781         {
782           context->preedit_chars[i + diff] = context->preedit_chars[i];
783           context->feedbacks[i + diff] = context->feedbacks[i];
784         }
785     }
786
787   for (i = 0; i < new_text_length; i++)
788     {
789       context->preedit_chars[chg_first + i] = new_text[i];
790       context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
791     }
792
793   context->preedit_length += diff;
794
795   if (new_text)
796     g_free (new_text);
797
798   g_signal_emit_by_name (context, "preedit_changed");
799 }
800     
801
802 static void
803 preedit_caret_callback (XIC                            xic,
804                         XPointer                       client_data,
805                         XIMPreeditCaretCallbackStruct *call_data)
806 {
807   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
808   
809   if (call_data->direction == XIMAbsolutePosition)
810     {
811       context->preedit_cursor = call_data->position;
812       g_signal_emit_by_name (context, "preedit_changed");
813     }
814   else
815     {
816       g_warning ("Caret movement command: %d %d %d not supported",
817                  call_data->position, call_data->direction, call_data->style);
818     }
819 }            
820
821 static void
822 status_start_callback (XIC      xic,
823                        XPointer client_data,
824                        XPointer call_data)
825 {
826   return;
827
828
829 static void
830 status_done_callback (XIC      xic,
831                       XPointer client_data,
832                       XPointer call_data)
833 {
834   return;
835 }
836
837 static void
838 status_draw_callback (XIC      xic,
839                       XPointer client_data,
840                       XIMStatusDrawCallbackStruct *call_data)
841 {
842   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
843
844   if (call_data->type == XIMTextType)
845     {
846       gchar *text;
847       xim_text_to_utf8 (context, call_data->data.text, &text);
848
849       if (text)
850         status_window_set_text (context, text);
851       else
852         status_window_set_text (context, "");
853     }
854   else                          /* bitmap */
855     {
856       g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
857     }
858 }
859
860 static XIC
861 get_ic_with_preedit (GtkIMContextXIM *context_xim)
862 {
863   XIC xic = 0;
864   const char *name1 = NULL;
865   XVaNestedList list1 = NULL;
866   const char *name2 = NULL;
867   XVaNestedList list2 = NULL;
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       name1 = XNPreeditAttributes;
880       list1 = XVaCreateNestedList (0,
881                                    XNPreeditStartCallback, &context_xim->preedit_start_callback,
882                                    XNPreeditDoneCallback, &context_xim->preedit_done_callback,
883                                    XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
884                                    XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
885                                    NULL);
886     }
887
888   if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
889     {
890       XVaNestedList status_attrs;
891
892       context_xim->status_start_callback.client_data = (XPointer)context_xim;
893       context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
894       context_xim->status_done_callback.client_data = (XPointer)context_xim;
895       context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
896       context_xim->status_draw_callback.client_data = (XPointer)context_xim;
897       context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
898           
899       status_attrs = XVaCreateNestedList (0,
900                                           XNStatusStartCallback, &context_xim->status_start_callback,
901                                           XNStatusDoneCallback, &context_xim->status_done_callback,
902                                           XNStatusDrawCallback, &context_xim->status_draw_callback,
903                                           NULL);
904       if (name1 == NULL)
905         {
906           name1 = XNStatusAttributes;
907           list1 = status_attrs;
908         }
909       else
910         {
911           name2 = XNStatusAttributes;
912           list2 = status_attrs;
913         }
914     }
915
916   xic = XCreateIC (context_xim->im_info->im,
917                    XNInputStyle, context_xim->im_info->style,
918                    XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
919                    name1, list1,
920                    name2, list2,
921                    NULL);
922   if (list1)
923     XFree (list1);
924   if (list2)
925     XFree (list2);
926   
927   return xic;
928 }
929
930 static XIC
931 get_ic_without_preedit (GtkIMContextXIM *context_xim)
932 {
933   return XCreateIC (context_xim->im_info->im,
934                     XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
935                     XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
936                     NULL);
937 }
938
939 static XIC
940 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
941 {
942   if (!context_xim->ic && context_xim->im_info)
943     {
944       if (!context_xim->use_preedit)
945         context_xim->ic = get_ic_without_preedit (context_xim);
946       else
947         context_xim->ic = get_ic_with_preedit (context_xim);
948
949       if (context_xim->ic)
950         {
951           /* Don't filter key released events with XFilterEvents unless
952            * input methods ask for. This is a workaround for Solaris input
953            * method bug in C and European locales. It doubles each key
954            * stroke if both key pressed and released events are filtered.
955            * (bugzilla #81759)
956            */
957           gulong mask = 0;
958           XGetICValues (context_xim->ic,
959                         XNFilterEvents, &mask,
960                         NULL);
961           context_xim->filter_key_release = (mask & KeyReleaseMask);
962         }
963     }
964   return context_xim->ic;
965 }
966
967 /**************************
968  *                        *
969  * Status Window handling *
970  *                        *
971  **************************/
972
973 static gboolean
974 status_window_expose_event (GtkWidget      *widget,
975                             GdkEventExpose *event)
976 {
977   gdk_draw_rectangle (widget->window,
978                       widget->style->base_gc [GTK_STATE_NORMAL],
979                       TRUE,
980                       0, 0,
981                       widget->allocation.width, widget->allocation.height);
982   gdk_draw_rectangle (widget->window,
983                       widget->style->text_gc [GTK_STATE_NORMAL],
984                       FALSE,
985                       0, 0,
986                       widget->allocation.width - 1, widget->allocation.height - 1);
987
988   return FALSE;
989 }
990
991 static void
992 status_window_style_set (GtkWidget *toplevel,
993                          GtkStyle  *previous_style,
994                          GtkWidget *label)
995 {
996   gint i;
997   
998   for (i = 0; i < 5; i++)
999     gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
1000 }
1001
1002 /* Frees a status window and removes its link from the status_windows list */
1003 static void
1004 status_window_free (StatusWindow *status_window)
1005 {
1006   status_windows = g_slist_remove (status_windows, status_window);
1007  
1008   g_signal_handler_disconnect (status_window->toplevel, status_window->destroy_handler_id);
1009   g_signal_handler_disconnect (status_window->toplevel, status_window->configure_handler_id);
1010   gtk_widget_destroy (status_window->window);
1011   g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
1012  
1013   g_free (status_window);
1014 }
1015
1016 static gboolean
1017 status_window_configure (GtkWidget         *toplevel,
1018                          GdkEventConfigure *event,
1019                          StatusWindow      *status_window)
1020 {
1021   GdkRectangle rect;
1022   GtkRequisition requisition;
1023   gint y;
1024   gint height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
1025   
1026   gdk_window_get_frame_extents (toplevel->window, &rect);
1027   gtk_widget_size_request (status_window->window, &requisition);
1028
1029   if (rect.y + rect.height + requisition.height < height)
1030     y = rect.y + rect.height;
1031   else
1032     y = height - requisition.height;
1033   
1034   gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
1035
1036   return FALSE;
1037 }
1038
1039 static GtkWidget *
1040 status_window_get (GtkIMContextXIM *context_xim,
1041                    gboolean         create)
1042 {
1043   GdkWindow *toplevel_gdk;
1044   GtkWidget *toplevel;
1045   GtkWidget *window;
1046   StatusWindow *status_window;
1047   GtkWidget *status_label;
1048   
1049   if (!context_xim->client_window)
1050     return NULL;
1051
1052   toplevel_gdk = context_xim->client_window;
1053   while (TRUE)
1054     {
1055       GdkWindow *parent = gdk_window_get_parent (toplevel_gdk);
1056       if (parent == gdk_get_default_root_window ())
1057         break;
1058       else
1059         toplevel_gdk = parent;
1060     }
1061
1062   gdk_window_get_user_data (toplevel_gdk, (gpointer *)&toplevel);
1063   if (!toplevel)
1064     return NULL;
1065
1066   status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
1067   if (status_window)
1068     return status_window->window;
1069   else if (!create)
1070     return NULL;
1071
1072   status_window = g_new (StatusWindow, 1);
1073   status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
1074   status_window->toplevel = toplevel;
1075
1076   status_windows = g_slist_prepend (status_windows, status_window);
1077
1078   window = status_window->window;
1079
1080   gtk_window_set_policy (GTK_WINDOW (window), FALSE, FALSE, FALSE);
1081   gtk_widget_set_app_paintable (window, TRUE);
1082
1083   status_label = gtk_label_new ("");
1084   gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
1085   gtk_widget_show (status_label);
1086   
1087   gtk_container_add (GTK_CONTAINER (window), status_label);
1088
1089   status_window->destroy_handler_id = g_signal_connect_swapped (toplevel, "destroy",
1090                                                                 G_CALLBACK (status_window_free),
1091                                                                 status_window);
1092   status_window->configure_handler_id = g_signal_connect (toplevel, "configure_event",
1093                                                           G_CALLBACK (status_window_configure),
1094                                                           status_window);
1095
1096   status_window_configure (toplevel, NULL, status_window);
1097
1098   g_signal_connect (window, "style_set",
1099                     G_CALLBACK (status_window_style_set), status_label);
1100   g_signal_connect (window, "expose_event",
1101                     G_CALLBACK (status_window_expose_event), NULL);
1102
1103   g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
1104
1105   return window;
1106 }
1107
1108 static gboolean
1109 status_window_has_text (GtkWidget *status_window)
1110 {
1111   GtkWidget *label = GTK_BIN (status_window)->child;
1112   const gchar *text = gtk_label_get_text (GTK_LABEL (label));
1113
1114   return text[0] != '\0';
1115 }
1116
1117 static void
1118 status_window_show (GtkIMContextXIM *context_xim)
1119 {
1120   GtkWidget *status_window = status_window_get (context_xim, TRUE);
1121
1122   context_xim->status_visible = TRUE;
1123   
1124   if (status_window && status_window_has_text (status_window))
1125     gtk_widget_show (status_window);
1126 }
1127
1128 static void
1129 status_window_hide (GtkIMContextXIM *context_xim)
1130 {
1131   GtkWidget *status_window = status_window_get (context_xim, FALSE);
1132
1133   context_xim->status_visible = FALSE;
1134   
1135   if (status_window)
1136     gtk_widget_hide (status_window);
1137 }
1138
1139 static void
1140 status_window_set_text (GtkIMContextXIM *context_xim,
1141                         const gchar     *text)
1142 {
1143   GtkWidget *status_window = status_window_get (context_xim, TRUE);
1144
1145   if (status_window)
1146     {
1147       GtkWidget *label = GTK_BIN (status_window)->child;
1148       gtk_label_set_text (GTK_LABEL (label), text);
1149       
1150       if (context_xim->status_visible && status_window_has_text (status_window))
1151         gtk_widget_show (status_window);
1152       else
1153         gtk_widget_hide (status_window);
1154     }
1155 }
1156
1157 /**
1158  * gtk_im_context_xim_shutdown:
1159  * 
1160  * Destroys all the status windows that are kept by the XIM contexts.  This
1161  * function should only be called by the XIM module exit routine.
1162  **/
1163 void
1164 gtk_im_context_xim_shutdown (void)
1165 {
1166   while (status_windows)
1167     status_window_free (status_windows->data);
1168 }