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