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