]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextime.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / modules / input / gtkimcontextime.c
1 /*
2  * gtkimcontextime.c
3  * Copyright (C) 2003 Takuro Ashie
4  * Copyright (C) 2003-2004 Kazuki IWAMOTO
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 /*
22  *  Please see the following site for the detail of Windows IME API.
23  *  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/appendix/hh/appendix/imeimes2_35ph.asp
24  */
25
26 #ifdef GTK_DISABLE_DEPRECATED
27 #undef GTK_DISABLE_DEPRECATED
28 #endif
29
30 #include "gtkimcontextime.h"
31
32 #include "imm-extra.h"
33
34 #include "gdk/win32/gdkwin32.h"
35 #include "gdk/gdkkeysyms.h"
36
37 #include <pango/pango.h>
38
39 /* avoid warning */
40 #ifdef STRICT
41 # undef STRICT
42 # include <pango/pangowin32.h>
43 # ifndef STRICT
44 #   define STRICT 1
45 # endif
46 #else /* STRICT */
47 #   include <pango/pangowin32.h>
48 #endif /* STRICT */
49
50 /* #define BUFSIZE 4096 */
51
52 #define FREE_PREEDIT_BUFFER(ctx) \
53 {                                \
54   g_free((ctx)->priv->comp_str); \
55   g_free((ctx)->priv->read_str); \
56   (ctx)->priv->comp_str = NULL;  \
57   (ctx)->priv->read_str = NULL;  \
58   (ctx)->priv->comp_str_len = 0; \
59   (ctx)->priv->read_str_len = 0; \
60 }
61
62
63 struct _GtkIMContextIMEPrivate
64 {
65   /* save IME context when the client window is focused out */
66   DWORD conversion_mode;
67   DWORD sentence_mode;
68
69   LPVOID comp_str;
70   DWORD comp_str_len;
71   LPVOID read_str;
72   DWORD read_str_len;
73 };
74
75
76 /* GObject class methods */
77 static void gtk_im_context_ime_class_init (GtkIMContextIMEClass *class);
78 static void gtk_im_context_ime_init       (GtkIMContextIME      *context_ime);
79 static void gtk_im_context_ime_dispose    (GObject              *obj);
80 static void gtk_im_context_ime_finalize   (GObject              *obj);
81
82 static void gtk_im_context_ime_set_property (GObject      *object,
83                                              guint         prop_id,
84                                              const GValue *value,
85                                              GParamSpec   *pspec);
86 static void gtk_im_context_ime_get_property (GObject      *object,
87                                              guint         prop_id,
88                                              GValue       *value,
89                                              GParamSpec   *pspec);
90
91 /* GtkIMContext's virtual functions */
92 static void gtk_im_context_ime_set_client_window   (GtkIMContext *context,
93                                                     GdkWindow    *client_window);
94 static gboolean gtk_im_context_ime_filter_keypress (GtkIMContext   *context,
95                                                     GdkEventKey    *event);
96 static void gtk_im_context_ime_reset               (GtkIMContext   *context);
97 static void gtk_im_context_ime_get_preedit_string  (GtkIMContext   *context,
98                                                     gchar         **str,
99                                                     PangoAttrList **attrs,
100                                                     gint           *cursor_pos);
101 static void gtk_im_context_ime_focus_in            (GtkIMContext   *context);
102 static void gtk_im_context_ime_focus_out           (GtkIMContext   *context);
103 static void gtk_im_context_ime_set_cursor_location (GtkIMContext   *context,
104                                                     GdkRectangle   *area);
105 static void gtk_im_context_ime_set_use_preedit     (GtkIMContext   *context,
106                                                     gboolean        use_preedit);
107
108 /* GtkIMContextIME's private functions */
109 static void gtk_im_context_ime_set_preedit_font (GtkIMContext    *context);
110
111 static GdkFilterReturn
112 gtk_im_context_ime_message_filter               (GdkXEvent       *xevent,
113                                                  GdkEvent        *event,
114                                                  gpointer         data);
115 static void get_window_position                 (GdkWindow       *win,
116                                                  gint            *x,
117                                                  gint            *y);
118 static void cb_client_widget_hierarchy_changed  (GtkWidget       *widget,
119                                                  GtkWidget       *widget2,
120                                                  GtkIMContextIME *context_ime);
121
122 GType gtk_type_im_context_ime = 0;
123 static GObjectClass *parent_class;
124
125
126 void
127 gtk_im_context_ime_register_type (GTypeModule *type_module)
128 {
129   const GTypeInfo im_context_ime_info = {
130     sizeof (GtkIMContextIMEClass),
131     (GBaseInitFunc) NULL,
132     (GBaseFinalizeFunc) NULL,
133     (GClassInitFunc) gtk_im_context_ime_class_init,
134     NULL,                       /* class_finalize */
135     NULL,                       /* class_data */
136     sizeof (GtkIMContextIME),
137     0,
138     (GInstanceInitFunc) gtk_im_context_ime_init,
139   };
140
141   gtk_type_im_context_ime =
142     g_type_module_register_type (type_module,
143                                  GTK_TYPE_IM_CONTEXT,
144                                  "GtkIMContextIME", &im_context_ime_info, 0);
145 }
146
147 static void
148 gtk_im_context_ime_class_init (GtkIMContextIMEClass *class)
149 {
150   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
151   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
152
153   parent_class = g_type_class_peek_parent (class);
154
155   gobject_class->finalize     = gtk_im_context_ime_finalize;
156   gobject_class->dispose      = gtk_im_context_ime_dispose;
157   gobject_class->set_property = gtk_im_context_ime_set_property;
158   gobject_class->get_property = gtk_im_context_ime_get_property;
159
160   im_context_class->set_client_window   = gtk_im_context_ime_set_client_window;
161   im_context_class->filter_keypress     = gtk_im_context_ime_filter_keypress;
162   im_context_class->reset               = gtk_im_context_ime_reset;
163   im_context_class->get_preedit_string  = gtk_im_context_ime_get_preedit_string;
164   im_context_class->focus_in            = gtk_im_context_ime_focus_in;
165   im_context_class->focus_out           = gtk_im_context_ime_focus_out;
166   im_context_class->set_cursor_location = gtk_im_context_ime_set_cursor_location;
167   im_context_class->set_use_preedit     = gtk_im_context_ime_set_use_preedit;
168 }
169
170
171 static void
172 gtk_im_context_ime_init (GtkIMContextIME *context_ime)
173 {
174   context_ime->client_window          = NULL;
175   context_ime->toplevel               = NULL;
176   context_ime->use_preedit            = TRUE;
177   context_ime->preediting             = FALSE;
178   context_ime->opened                 = FALSE;
179   context_ime->focus                  = FALSE;
180   context_ime->cursor_location.x      = 0;
181   context_ime->cursor_location.y      = 0;
182   context_ime->cursor_location.width  = 0;
183   context_ime->cursor_location.height = 0;
184
185   context_ime->priv = g_malloc0 (sizeof (GtkIMContextIMEPrivate));
186   context_ime->priv->conversion_mode  = 0;
187   context_ime->priv->sentence_mode    = 0;
188   context_ime->priv->comp_str         = NULL;
189   context_ime->priv->comp_str_len     = 0;
190   context_ime->priv->read_str         = NULL;
191   context_ime->priv->read_str_len     = 0;
192 }
193
194
195 static void
196 gtk_im_context_ime_dispose (GObject *obj)
197 {
198   GtkIMContext *context = GTK_IM_CONTEXT (obj);
199   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (obj);
200
201   if (context_ime->client_window)
202     gtk_im_context_ime_set_client_window (context, NULL);
203
204   FREE_PREEDIT_BUFFER (context_ime);
205
206   if (G_OBJECT_CLASS (parent_class)->dispose)
207     G_OBJECT_CLASS (parent_class)->dispose (obj);
208 }
209
210
211 static void
212 gtk_im_context_ime_finalize (GObject *obj)
213 {
214   /* GtkIMContext *context = GTK_IM_CONTEXT (obj); */
215   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (obj);
216
217   g_free (context_ime->priv);
218   context_ime->priv = NULL;
219
220   if (G_OBJECT_CLASS (parent_class)->finalize)
221     G_OBJECT_CLASS (parent_class)->finalize (obj);
222 }
223
224
225 static void
226 gtk_im_context_ime_set_property (GObject      *object,
227                                  guint         prop_id,
228                                  const GValue *value,
229                                  GParamSpec   *pspec)
230 {
231   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (object);
232
233   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime));
234
235   switch (prop_id)
236     {
237     default:
238       break;
239     }
240 }
241
242
243 static void
244 gtk_im_context_ime_get_property (GObject    *object,
245                                  guint       prop_id,
246                                  GValue     *value,
247                                  GParamSpec *pspec)
248 {
249   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (object);
250
251   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime));
252
253   switch (prop_id)
254     {
255     default:
256       break;
257     }
258 }
259
260
261 GtkIMContext *
262 gtk_im_context_ime_new (void)
263 {
264   return g_object_new (GTK_TYPE_IM_CONTEXT_IME, NULL);
265 }
266
267
268 static void
269 gtk_im_context_ime_set_client_window (GtkIMContext *context,
270                                       GdkWindow    *client_window)
271 {
272   GtkIMContextIME *context_ime;
273
274   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
275   context_ime = GTK_IM_CONTEXT_IME (context);
276
277   if (client_window)
278     {
279       HIMC himc;
280       HWND hwnd;
281
282       hwnd = gdk_win32_window_get_impl_hwnd (client_window);
283       himc = ImmGetContext (hwnd);
284       if (himc)
285         {
286           context_ime->opened = ImmGetOpenStatus (himc);
287           ImmGetConversionStatus (himc,
288                                   &context_ime->priv->conversion_mode,
289                                   &context_ime->priv->sentence_mode);
290           ImmReleaseContext (hwnd, himc);
291         }
292     }
293   else if (context_ime->focus)
294     {
295       gtk_im_context_ime_focus_out (context);
296     }
297
298   context_ime->client_window = client_window;
299 }
300
301
302 static gboolean
303 gtk_im_context_ime_filter_keypress (GtkIMContext *context,
304                                     GdkEventKey  *event)
305 {
306   GtkIMContextIME *context_ime;
307   gboolean retval = FALSE;
308   guint32 c;
309
310   g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (context), FALSE);
311   g_return_val_if_fail (event, FALSE);
312
313   if (event->type == GDK_KEY_RELEASE)
314     return FALSE;
315
316   if (event->state & GDK_CONTROL_MASK)
317     return FALSE;
318
319   context_ime = GTK_IM_CONTEXT_IME (context);
320
321   if (!context_ime->focus)
322     return FALSE;
323
324   if (!GDK_IS_WINDOW (context_ime->client_window))
325     return FALSE;
326
327   c = gdk_keyval_to_unicode (event->keyval);
328   if (c)
329     {
330       guchar utf8[10];
331       int len = g_unichar_to_utf8 (c, utf8);
332       utf8[len] = 0;
333
334       g_signal_emit_by_name (context_ime, "commit", utf8);
335       retval = TRUE;
336     }
337
338   return retval;
339 }
340
341
342 static void
343 gtk_im_context_ime_reset (GtkIMContext *context)
344 {
345   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
346   HWND hwnd;
347   HIMC himc;
348
349   if (!context_ime->client_window)
350     return;
351
352   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
353   himc = ImmGetContext (hwnd);
354   if (!himc)
355     return;
356
357   if (context_ime->preediting)
358     {
359       if (ImmGetOpenStatus (himc))
360         ImmNotifyIME (himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
361
362       context_ime->preediting = FALSE;
363       g_signal_emit_by_name (context, "preedit-changed");
364     }
365
366   ImmReleaseContext (hwnd, himc);
367 }
368
369
370 static gchar *
371 get_utf8_preedit_string (GtkIMContextIME *context_ime, gint *pos_ret)
372 {
373   gchar *utf8str = NULL;
374   HWND hwnd;
375   HIMC himc;
376   gint pos = 0;
377
378   if (pos_ret)
379     *pos_ret = 0;
380
381   if (!context_ime->client_window)
382     return g_strdup ("");
383   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
384   himc = ImmGetContext (hwnd);
385   if (!himc)
386     return g_strdup ("");
387
388   if (context_ime->preediting)
389     {
390       glong len;
391
392       len = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
393       if (len > 0)
394         {
395           GError *error = NULL;
396           gpointer buf = g_alloca (len);
397
398           ImmGetCompositionStringW (himc, GCS_COMPSTR, buf, len);
399           len /= 2;
400           utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
401           if (error)
402             {
403               g_warning ("%s", error->message);
404               g_error_free (error);
405             }
406           
407           if (pos_ret)
408             {
409               pos = ImmGetCompositionStringW (himc, GCS_CURSORPOS, NULL, 0);
410               if (pos < 0 || len < pos)
411                 {
412                   g_warning ("ImmGetCompositionString: "
413                              "Invalid cursor position!");
414                   pos = 0;
415                 }
416             }
417         }
418     }
419
420   if (!utf8str)
421     {
422       utf8str = g_strdup ("");
423       pos = 0;
424     }
425
426   if (pos_ret)
427     *pos_ret = pos;
428
429   ImmReleaseContext (hwnd, himc);
430
431   return utf8str;
432 }
433
434
435 static PangoAttrList *
436 get_pango_attr_list (GtkIMContextIME *context_ime, const gchar *utf8str)
437 {
438   PangoAttrList *attrs = pango_attr_list_new ();
439   HWND hwnd;
440   HIMC himc;
441
442   if (!context_ime->client_window)
443     return attrs;
444   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
445   himc = ImmGetContext (hwnd);
446   if (!himc)
447     return attrs;
448
449   if (context_ime->preediting)
450     {
451       const gchar *schr = utf8str, *echr;
452       guint8 *buf;
453       guint16 f_red, f_green, f_blue, b_red, b_green, b_blue;
454       glong len, spos = 0, epos, sidx = 0, eidx;
455       PangoAttribute *attr;
456
457       /*
458        *  get attributes list of IME.
459        */
460       len = ImmGetCompositionStringW (himc, GCS_COMPATTR, NULL, 0);
461       buf = g_alloca (len);
462       ImmGetCompositionStringW (himc, GCS_COMPATTR, buf, len);
463
464       /*
465        *  schr and echr are pointer in utf8str.
466        */
467       for (echr = g_utf8_next_char (utf8str); *schr != '\0';
468            echr = g_utf8_next_char (echr))
469         {
470           /*
471            *  spos and epos are buf(attributes list of IME) position by
472            *  bytes.
473            *  Using the wide-char API, this value is same with UTF-8 offset.
474            */
475           epos = g_utf8_pointer_to_offset (utf8str, echr);
476
477           /*
478            *  sidx and eidx are positions in utf8str by bytes.
479            */
480           eidx = echr - utf8str;
481
482           /*
483            *  convert attributes list to PangoAttriute.
484            */
485           if (*echr == '\0' || buf[spos] != buf[epos])
486             {
487               switch (buf[spos])
488                 {
489                 case ATTR_TARGET_CONVERTED:
490                   attr = pango_attr_underline_new (PANGO_UNDERLINE_DOUBLE);
491                   attr->start_index = sidx;
492                   attr->end_index = eidx;
493                   pango_attr_list_change (attrs, attr);
494                   f_red = f_green = f_blue = 0;
495                   b_red = b_green = b_blue = 0xffff;
496                   break;
497                 case ATTR_TARGET_NOTCONVERTED:
498                   f_red = f_green = f_blue = 0xffff;
499                   b_red = b_green = b_blue = 0;
500                   break;
501                 case ATTR_INPUT_ERROR:
502                   f_red = f_green = f_blue = 0;
503                   b_red = b_green = b_blue = 0x7fff;
504                   break;
505                 default:        /* ATTR_INPUT,ATTR_CONVERTED,ATTR_FIXEDCONVERTED */
506                   attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
507                   attr->start_index = sidx;
508                   attr->end_index = eidx;
509                   pango_attr_list_change (attrs, attr);
510                   f_red = f_green = f_blue = 0;
511                   b_red = b_green = b_blue = 0xffff;
512                 }
513               attr = pango_attr_foreground_new (f_red, f_green, f_blue);
514               attr->start_index = sidx;
515               attr->end_index = eidx;
516               pango_attr_list_change (attrs, attr);
517               attr = pango_attr_background_new (b_red, b_green, b_blue);
518               attr->start_index = sidx;
519               attr->end_index = eidx;
520               pango_attr_list_change (attrs, attr);
521
522               schr = echr;
523               spos = epos;
524               sidx = eidx;
525             }
526         }
527     }
528
529   ImmReleaseContext (hwnd, himc);
530
531   return attrs;
532 }
533
534
535 static void
536 gtk_im_context_ime_get_preedit_string (GtkIMContext   *context,
537                                        gchar         **str,
538                                        PangoAttrList **attrs,
539                                        gint           *cursor_pos)
540 {
541   gchar *utf8str = NULL;
542   gint pos = 0;
543   GtkIMContextIME *context_ime;
544
545   context_ime = GTK_IM_CONTEXT_IME (context);
546
547   utf8str = get_utf8_preedit_string (context_ime, &pos);
548
549   if (attrs)
550     *attrs = get_pango_attr_list (context_ime, utf8str);
551
552   if (str)
553     {
554       *str = utf8str;
555     }
556   else
557     {
558       g_free (utf8str);
559       utf8str = NULL;
560     }
561
562   if (cursor_pos)
563     *cursor_pos = pos;
564 }
565
566
567 static void
568 gtk_im_context_ime_focus_in (GtkIMContext *context)
569 {
570   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
571   GdkWindow *toplevel;
572   GtkWidget *widget = NULL;
573   HWND hwnd, top_hwnd;
574   HIMC himc;
575
576   if (!GDK_IS_WINDOW (context_ime->client_window))
577     return;
578
579   /* swtich current context */
580   context_ime->focus = TRUE;
581
582   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
583   himc = ImmGetContext (hwnd);
584   if (!himc)
585     return;
586
587   toplevel = gdk_window_get_toplevel (context_ime->client_window);
588   if (GDK_IS_WINDOW (toplevel))
589     {
590       gdk_window_add_filter (toplevel,
591                              gtk_im_context_ime_message_filter, context_ime);
592       top_hwnd = gdk_win32_window_get_impl_hwnd (toplevel);
593
594       context_ime->toplevel = toplevel;
595     }
596   else
597     {
598       g_warning ("gtk_im_context_ime_focus_in(): "
599                  "cannot find toplevel window.");
600       return;
601     }
602
603   /* trace reparenting (probably no need) */
604   gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget);
605   if (GTK_IS_WIDGET (widget))
606     {
607       g_signal_connect (widget, "hierarchy-changed",
608                         G_CALLBACK (cb_client_widget_hierarchy_changed),
609                         context_ime);
610     }
611   else
612     {
613       /* warning? */
614     }
615
616   /* restore preedit context */
617   ImmSetConversionStatus (himc,
618                           context_ime->priv->conversion_mode,
619                           context_ime->priv->sentence_mode);
620
621   if (context_ime->opened)
622     {
623       if (!ImmGetOpenStatus (himc))
624         ImmSetOpenStatus (himc, TRUE);
625       if (context_ime->preediting)
626         {
627           ImmSetCompositionStringW (himc,
628                                     SCS_SETSTR,
629                                     context_ime->priv->comp_str,
630                                     context_ime->priv->comp_str_len, NULL, 0);
631           FREE_PREEDIT_BUFFER (context_ime);
632         }
633     }
634
635   /* clean */
636   ImmReleaseContext (hwnd, himc);
637 }
638
639
640 static void
641 gtk_im_context_ime_focus_out (GtkIMContext *context)
642 {
643   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
644   GdkWindow *toplevel;
645   GtkWidget *widget = NULL;
646   HWND hwnd, top_hwnd;
647   HIMC himc;
648
649   if (!GDK_IS_WINDOW (context_ime->client_window))
650     return;
651
652   /* swtich current context */
653   context_ime->focus = FALSE;
654
655   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
656   himc = ImmGetContext (hwnd);
657   if (!himc)
658     return;
659
660   /* save preedit context */
661   ImmGetConversionStatus (himc,
662                           &context_ime->priv->conversion_mode,
663                           &context_ime->priv->sentence_mode);
664
665   if (ImmGetOpenStatus (himc))
666     {
667       gboolean preediting = context_ime->preediting;
668
669       if (preediting)
670         {
671           FREE_PREEDIT_BUFFER (context_ime);
672
673           context_ime->priv->comp_str_len
674             = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
675           context_ime->priv->comp_str
676             = g_malloc (context_ime->priv->comp_str_len);
677           ImmGetCompositionStringW (himc, GCS_COMPSTR,
678                                     context_ime->priv->comp_str,
679                                     context_ime->priv->comp_str_len);
680
681           context_ime->priv->read_str_len
682             = ImmGetCompositionStringW (himc, GCS_COMPREADSTR, NULL, 0);
683           context_ime->priv->read_str
684             = g_malloc (context_ime->priv->read_str_len);
685           ImmGetCompositionStringW (himc, GCS_COMPREADSTR,
686                                     context_ime->priv->read_str,
687                                     context_ime->priv->read_str_len);
688         }
689
690       ImmSetOpenStatus (himc, FALSE);
691
692       context_ime->opened = TRUE;
693       context_ime->preediting = preediting;
694     }
695   else
696     {
697       context_ime->opened = FALSE;
698       context_ime->preediting = FALSE;
699     }
700
701   /* remove signal handler */
702   gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget);
703   if (GTK_IS_WIDGET (widget))
704     {
705       g_signal_handlers_disconnect_by_func
706         (G_OBJECT (widget),
707          G_CALLBACK (cb_client_widget_hierarchy_changed), context_ime);
708     }
709
710   /* remove event fileter */
711   toplevel = gdk_window_get_toplevel (context_ime->client_window);
712   if (GDK_IS_WINDOW (toplevel))
713     {
714       gdk_window_remove_filter (toplevel,
715                                 gtk_im_context_ime_message_filter,
716                                 context_ime);
717       top_hwnd = gdk_win32_window_get_impl_hwnd (toplevel);
718
719       context_ime->toplevel = NULL;
720     }
721   else
722     {
723       g_warning ("gtk_im_context_ime_focus_out(): "
724                  "cannot find toplevel window.");
725     }
726
727   /* clean */
728   ImmReleaseContext (hwnd, himc);
729 }
730
731
732 static void
733 gtk_im_context_ime_set_cursor_location (GtkIMContext *context,
734                                         GdkRectangle *area)
735 {
736   gint wx = 0, wy = 0;
737   GtkIMContextIME *context_ime;
738   COMPOSITIONFORM cf;
739   HWND hwnd;
740   HIMC himc;
741
742   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
743
744   context_ime = GTK_IM_CONTEXT_IME (context);
745   if (area)
746     context_ime->cursor_location = *area;
747
748   if (!context_ime->client_window)
749     return;
750
751   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
752   himc = ImmGetContext (hwnd);
753   if (!himc)
754     return;
755
756   get_window_position (context_ime->client_window, &wx, &wy);
757   cf.dwStyle = CFS_POINT;
758   cf.ptCurrentPos.x = wx + context_ime->cursor_location.x;
759   cf.ptCurrentPos.y = wy + context_ime->cursor_location.y;
760   ImmSetCompositionWindow (himc, &cf);
761
762   ImmReleaseContext (hwnd, himc);
763 }
764
765
766 static void
767 gtk_im_context_ime_set_use_preedit (GtkIMContext *context,
768                                     gboolean      use_preedit)
769 {
770   GtkIMContextIME *context_ime;
771
772   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
773   context_ime = GTK_IM_CONTEXT_IME (context);
774
775   context_ime->use_preedit = use_preedit;
776   if (context_ime->preediting)
777     {
778       HWND hwnd;
779       HIMC himc;
780
781       hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
782       himc = ImmGetContext (hwnd);
783       if (!himc)
784         return;
785
786       /* FIXME: What to do? */
787
788       ImmReleaseContext (hwnd, himc);
789     }
790 }
791
792
793 static void
794 gtk_im_context_ime_set_preedit_font (GtkIMContext *context)
795 {
796   GtkIMContextIME *context_ime;
797   GtkWidget *widget = NULL;
798   HWND hwnd;
799   HIMC himc;
800   HKL ime = GetKeyboardLayout (0);
801   const gchar *lang;
802   gunichar wc;
803   PangoContext *pango_context;
804   PangoFont *font;
805   LOGFONT *logfont;
806
807   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
808
809   context_ime = GTK_IM_CONTEXT_IME (context);
810   if (!context_ime->client_window)
811     return;
812
813   gdk_window_get_user_data (context_ime->client_window, (gpointer) &widget);
814   if (!GTK_IS_WIDGET (widget))
815     return;
816
817   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
818   himc = ImmGetContext (hwnd);
819   if (!himc)
820     return;
821
822   /* set font */
823   pango_context = gtk_widget_get_pango_context (widget);
824   if (!pango_context)
825     goto ERROR_OUT;
826
827   /* Try to make sure we use a font that actually can show the
828    * language in question.
829    */ 
830
831   switch (PRIMARYLANGID (LOWORD (ime)))
832     {
833     case LANG_JAPANESE:
834       lang = "ja"; break;
835     case LANG_KOREAN:
836       lang = "ko"; break;
837     case LANG_CHINESE:
838       switch (SUBLANGID (LOWORD (ime)))
839         {
840         case SUBLANG_CHINESE_TRADITIONAL:
841           lang = "zh_TW"; break;
842         case SUBLANG_CHINESE_SIMPLIFIED:
843           lang = "zh_CN"; break;
844         case SUBLANG_CHINESE_HONGKONG:
845           lang = "zh_HK"; break;
846         case SUBLANG_CHINESE_SINGAPORE:
847           lang = "zh_SG"; break;
848         case SUBLANG_CHINESE_MACAU:
849           lang = "zh_MO"; break;
850         default:
851           lang = "zh"; break;
852         }
853       break;
854     default:
855       lang = ""; break;
856     }
857   
858   if (lang[0])
859     {
860       /* We know what language it is. Look for a character, any
861        * character, that language needs.
862        */
863       PangoLanguage *pango_lang = pango_language_from_string (lang);
864       PangoFontset *fontset =
865         pango_context_load_fontset (pango_context,
866                                     gtk_widget_get_style (widget)->font_desc,
867                                     pango_lang);
868       gunichar *sample =
869         g_utf8_to_ucs4 (pango_language_get_sample_string (pango_lang),
870                         -1, NULL, NULL, NULL);
871       wc = 0x4E00;              /* In all CJK languages? */
872       if (sample != NULL)
873         {
874           int i;
875
876           for (i = 0; sample[i]; i++)
877             if (g_unichar_iswide (sample[i]))
878               {
879                 wc = sample[i];
880                 break;
881               }
882           g_free (sample);
883         }
884       font = pango_fontset_get_font (fontset, wc);
885       g_object_unref (fontset);
886     }
887   else
888     font = pango_context_load_font (pango_context, gtk_widget_get_style (widget)->font_desc);
889
890   if (!font)
891     goto ERROR_OUT;
892
893   logfont = pango_win32_font_logfont (font);
894   if (logfont)
895     ImmSetCompositionFont (himc, logfont);
896
897   g_object_unref (font);
898
899 ERROR_OUT:
900   /* clean */
901   ImmReleaseContext (hwnd, himc);
902 }
903
904
905 static GdkFilterReturn
906 gtk_im_context_ime_message_filter (GdkXEvent *xevent,
907                                    GdkEvent  *event,
908                                    gpointer   data)
909 {
910   GtkIMContext *context;
911   GtkIMContextIME *context_ime;
912   HWND hwnd;
913   HIMC himc;
914   MSG *msg = (MSG *) xevent;
915   GdkFilterReturn retval = GDK_FILTER_CONTINUE;
916
917   g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (data), retval);
918
919   context = GTK_IM_CONTEXT (data);
920   context_ime = GTK_IM_CONTEXT_IME (data);
921   if (!context_ime->focus)
922     return retval;
923
924   hwnd = gdk_win32_window_get_impl_hwnd (context_ime->client_window);
925   himc = ImmGetContext (hwnd);
926   if (!himc)
927     return retval;
928
929   switch (msg->message)
930     {
931     case WM_IME_COMPOSITION:
932       {
933         gint wx = 0, wy = 0;
934         CANDIDATEFORM cf;
935
936         get_window_position (context_ime->client_window, &wx, &wy);
937         /* FIXME! */
938         {
939           HWND hwnd_top;
940           POINT pt;
941           RECT rc;
942
943           hwnd_top =
944             gdk_win32_window_get_impl_hwnd (gdk_window_get_toplevel
945                                             (context_ime->client_window));
946           GetWindowRect (hwnd_top, &rc);
947           pt.x = wx;
948           pt.y = wy;
949           ClientToScreen (hwnd_top, &pt);
950           wx = pt.x - rc.left;
951           wy = pt.y - rc.top;
952         }
953         cf.dwIndex = 0;
954         cf.dwStyle = CFS_CANDIDATEPOS;
955         cf.ptCurrentPos.x = wx + context_ime->cursor_location.x;
956         cf.ptCurrentPos.y = wy + context_ime->cursor_location.y
957           + context_ime->cursor_location.height;
958         ImmSetCandidateWindow (himc, &cf);
959
960         if ((msg->lParam & GCS_COMPSTR))
961           g_signal_emit_by_name (context, "preedit-changed");
962
963         if (msg->lParam & GCS_RESULTSTR)
964           {
965             gsize len;
966             gchar *utf8str = NULL;
967             GError *error = NULL;
968
969             len = ImmGetCompositionStringW (himc, GCS_RESULTSTR, NULL, 0);
970
971             if (len > 0)
972               {
973                 gpointer buf = g_alloca (len);
974                 ImmGetCompositionStringW (himc, GCS_RESULTSTR, buf, len);
975                 len /= 2;
976                 utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
977                 if (error)
978                   {
979                     g_warning ("%s", error->message);
980                     g_error_free (error);
981                   }
982               }
983
984             if (utf8str)
985               {
986                 g_signal_emit_by_name (context, "commit", utf8str);
987                 g_free (utf8str);
988                 retval = TRUE;
989               }
990           }
991
992         if (context_ime->use_preedit)
993           retval = TRUE;
994         break;
995       }
996
997     case WM_IME_STARTCOMPOSITION:
998       context_ime->preediting = TRUE;
999       gtk_im_context_ime_set_cursor_location (context, NULL);
1000       g_signal_emit_by_name (context, "preedit-start");
1001       if (context_ime->use_preedit)
1002         retval = TRUE;
1003       break;
1004
1005     case WM_IME_ENDCOMPOSITION:
1006       context_ime->preediting = FALSE;
1007       g_signal_emit_by_name (context, "preedit-changed");
1008       g_signal_emit_by_name (context, "preedit-end");
1009       if (context_ime->use_preedit)
1010         retval = TRUE;
1011       break;
1012
1013     case WM_IME_NOTIFY:
1014       switch (msg->wParam)
1015         {
1016         case IMN_SETOPENSTATUS:
1017           context_ime->opened = ImmGetOpenStatus (himc);
1018           gtk_im_context_ime_set_preedit_font (context);
1019           break;
1020
1021         default:
1022           break;
1023         }
1024
1025     default:
1026       break;
1027     }
1028
1029   ImmReleaseContext (hwnd, himc);
1030   return retval;
1031 }
1032
1033
1034 /*
1035  * x and y must be initialized to 0.
1036  */
1037 static void
1038 get_window_position (GdkWindow *win, gint *x, gint *y)
1039 {
1040   GdkWindow *parent, *toplevel;
1041   gint wx, wy;
1042
1043   g_return_if_fail (GDK_IS_WINDOW (win));
1044   g_return_if_fail (x && y);
1045
1046   gdk_window_get_position (win, &wx, &wy);
1047   *x += wx;
1048   *y += wy;
1049   parent = gdk_window_get_parent (win);
1050   toplevel = gdk_window_get_toplevel (win);
1051
1052   if (parent && parent != toplevel)
1053     get_window_position (parent, x, y);
1054 }
1055
1056
1057 /*
1058  *  probably, this handler isn't needed.
1059  */
1060 static void
1061 cb_client_widget_hierarchy_changed (GtkWidget       *widget,
1062                                     GtkWidget       *widget2,
1063                                     GtkIMContextIME *context_ime)
1064 {
1065   GdkWindow *new_toplevel;
1066
1067   g_return_if_fail (GTK_IS_WIDGET (widget));
1068   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime));
1069
1070   if (!context_ime->client_window)
1071     return;
1072   if (!context_ime->focus)
1073     return;
1074
1075   new_toplevel = gdk_window_get_toplevel (context_ime->client_window);
1076   if (context_ime->toplevel == new_toplevel)
1077     return;
1078
1079   /* remove filter from old toplevel */
1080   if (GDK_IS_WINDOW (context_ime->toplevel))
1081     {
1082       gdk_window_remove_filter (context_ime->toplevel,
1083                                 gtk_im_context_ime_message_filter,
1084                                 context_ime);
1085     }
1086   else
1087     {
1088     }
1089
1090   /* add filter to new toplevel */
1091   if (GDK_IS_WINDOW (new_toplevel))
1092     {
1093       gdk_window_add_filter (new_toplevel,
1094                              gtk_im_context_ime_message_filter, context_ime);
1095     }
1096   else
1097     {
1098     }
1099
1100   context_ime->toplevel = new_toplevel;
1101 }