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