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