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