]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextime.c
009af3febe08f1b495deea2f908dffbf30fedfac
[~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   if (!context_ime->client_window)
379     return g_strdup ("");
380   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
381   himc = ImmGetContext (hwnd);
382   if (!himc)
383     return g_strdup ("");
384
385   if (context_ime->preediting)
386     {
387       glong len;
388
389       len = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
390       if (len > 0)
391         {
392           GError *error = NULL;
393           gpointer buf = g_alloca (len);
394
395           ImmGetCompositionStringW (himc, GCS_COMPSTR, buf, len);
396           len /= 2;
397           utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
398           if (error)
399             {
400               g_warning ("%s", error->message);
401               g_error_free (error);
402             }
403           
404           if (pos_ret)
405             {
406               pos = ImmGetCompositionStringW (himc, GCS_CURSORPOS, NULL, 0);
407               if (pos < 0 || len < pos)
408                 {
409                   g_warning ("ImmGetCompositionString: "
410                              "Invalid cursor position!");
411                   pos = 0;
412                 }
413             }
414         }
415     }
416
417   if (!utf8str)
418     {
419       utf8str = g_strdup ("");
420       pos = 0;
421     }
422
423   if (pos_ret)
424     *pos_ret = pos;
425
426   ImmReleaseContext (hwnd, himc);
427
428   return utf8str;
429 }
430
431
432 static PangoAttrList *
433 get_pango_attr_list (GtkIMContextIME *context_ime, const gchar *utf8str)
434 {
435   PangoAttrList *attrs = pango_attr_list_new ();
436   HWND hwnd;
437   HIMC himc;
438
439   if (!context_ime->client_window)
440     return attrs;
441   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
442   himc = ImmGetContext (hwnd);
443   if (!himc)
444     return attrs;
445
446   if (context_ime->preediting)
447     {
448       const gchar *schr = utf8str, *echr;
449       guint8 *buf;
450       guint16 f_red, f_green, f_blue, b_red, b_green, b_blue;
451       glong len, spos = 0, epos, sidx = 0, eidx;
452       PangoAttribute *attr;
453
454       /*
455        *  get attributes list of IME.
456        */
457       len = ImmGetCompositionStringW (himc, GCS_COMPATTR, NULL, 0);
458       buf = g_alloca (len);
459       ImmGetCompositionStringW (himc, GCS_COMPATTR, buf, len);
460
461       /*
462        *  schr and echr are pointer in utf8str.
463        */
464       for (echr = g_utf8_next_char (utf8str); *schr != '\0';
465            echr = g_utf8_next_char (echr))
466         {
467           /*
468            *  spos and epos are buf(attributes list of IME) position by
469            *  bytes.
470            *  Using the wide-char API, this value is same with UTF-8 offset.
471            */
472           epos = g_utf8_pointer_to_offset (utf8str, echr);
473
474           /*
475            *  sidx and eidx are positions in utf8str by bytes.
476            */
477           eidx = echr - utf8str;
478
479           /*
480            *  convert attributes list to PangoAttriute.
481            */
482           if (*echr == '\0' || buf[spos] != buf[epos])
483             {
484               switch (buf[spos])
485                 {
486                 case ATTR_TARGET_CONVERTED:
487                   attr = pango_attr_underline_new (PANGO_UNDERLINE_DOUBLE);
488                   attr->start_index = sidx;
489                   attr->end_index = eidx;
490                   pango_attr_list_change (attrs, attr);
491                   f_red = f_green = f_blue = 0;
492                   b_red = b_green = b_blue = 0xffff;
493                   break;
494                 case ATTR_TARGET_NOTCONVERTED:
495                   f_red = f_green = f_blue = 0xffff;
496                   b_red = b_green = b_blue = 0;
497                   break;
498                 case ATTR_INPUT_ERROR:
499                   f_red = f_green = f_blue = 0;
500                   b_red = b_green = b_blue = 0x7fff;
501                   break;
502                 default:        /* ATTR_INPUT,ATTR_CONVERTED,ATTR_FIXEDCONVERTED */
503                   attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
504                   attr->start_index = sidx;
505                   attr->end_index = eidx;
506                   pango_attr_list_change (attrs, attr);
507                   f_red = f_green = f_blue = 0;
508                   b_red = b_green = b_blue = 0xffff;
509                 }
510               attr = pango_attr_foreground_new (f_red, f_green, f_blue);
511               attr->start_index = sidx;
512               attr->end_index = eidx;
513               pango_attr_list_change (attrs, attr);
514               attr = pango_attr_background_new (b_red, b_green, b_blue);
515               attr->start_index = sidx;
516               attr->end_index = eidx;
517               pango_attr_list_change (attrs, attr);
518
519               schr = echr;
520               spos = epos;
521               sidx = eidx;
522             }
523         }
524     }
525
526   ImmReleaseContext (hwnd, himc);
527
528   return attrs;
529 }
530
531
532 static void
533 gtk_im_context_ime_get_preedit_string (GtkIMContext   *context,
534                                        gchar         **str,
535                                        PangoAttrList **attrs,
536                                        gint           *cursor_pos)
537 {
538   gchar *utf8str = NULL;
539   gint pos = 0;
540   GtkIMContextIME *context_ime;
541
542   context_ime = GTK_IM_CONTEXT_IME (context);
543
544   utf8str = get_utf8_preedit_string (context_ime, &pos);
545
546   if (attrs)
547     *attrs = get_pango_attr_list (context_ime, utf8str);
548
549   if (str)
550     {
551       *str = utf8str;
552     }
553   else
554     {
555       g_free (utf8str);
556       utf8str = NULL;
557     }
558
559   if (cursor_pos)
560     *cursor_pos = pos;
561 }
562
563
564 static void
565 gtk_im_context_ime_focus_in (GtkIMContext *context)
566 {
567   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
568   GdkWindow *toplevel;
569   GtkWidget *widget = NULL;
570   HWND hwnd, top_hwnd;
571   HIMC himc;
572
573   if (!GDK_IS_WINDOW (context_ime->client_window))
574     return;
575
576   /* swtich current context */
577   context_ime->focus = TRUE;
578
579   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
580   himc = ImmGetContext (hwnd);
581   if (!himc)
582     return;
583
584   toplevel = gdk_window_get_toplevel (context_ime->client_window);
585   if (GDK_IS_WINDOW (toplevel))
586     {
587       gdk_window_add_filter (toplevel,
588                              gtk_im_context_ime_message_filter, context_ime);
589       top_hwnd = GDK_WINDOW_HWND (toplevel);
590
591       context_ime->toplevel = toplevel;
592     }
593   else
594     {
595       g_warning ("gtk_im_context_ime_focus_in(): "
596                  "cannot find toplevel window.");
597       return;
598     }
599
600   /* trace reparenting (probably no need) */
601   gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget);
602   if (GTK_IS_WIDGET (widget))
603     {
604       g_signal_connect (widget, "hierarchy-changed",
605                         G_CALLBACK (cb_client_widget_hierarchy_changed),
606                         context_ime);
607     }
608   else
609     {
610       /* warning? */
611     }
612
613   /* restore preedit context */
614   ImmSetConversionStatus (himc,
615                           context_ime->priv->conversion_mode,
616                           context_ime->priv->sentence_mode);
617
618   if (context_ime->opened)
619     {
620       if (!ImmGetOpenStatus (himc))
621         ImmSetOpenStatus (himc, TRUE);
622       if (context_ime->preediting)
623         {
624           ImmSetCompositionStringW (himc,
625                                     SCS_SETSTR,
626                                     context_ime->priv->comp_str,
627                                     context_ime->priv->comp_str_len, NULL, 0);
628           FREE_PREEDIT_BUFFER (context_ime);
629         }
630     }
631
632   /* clean */
633   ImmReleaseContext (hwnd, himc);
634 }
635
636
637 static void
638 gtk_im_context_ime_focus_out (GtkIMContext *context)
639 {
640   GtkIMContextIME *context_ime = GTK_IM_CONTEXT_IME (context);
641   GdkWindow *toplevel;
642   GtkWidget *widget = NULL;
643   HWND hwnd, top_hwnd;
644   HIMC himc;
645
646   if (!GDK_IS_WINDOW (context_ime->client_window))
647     return;
648
649   /* swtich current context */
650   context_ime->focus = FALSE;
651
652   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
653   himc = ImmGetContext (hwnd);
654   if (!himc)
655     return;
656
657   /* save preedit context */
658   ImmGetConversionStatus (himc,
659                           &context_ime->priv->conversion_mode,
660                           &context_ime->priv->sentence_mode);
661
662   if (ImmGetOpenStatus (himc))
663     {
664       gboolean preediting = context_ime->preediting;
665
666       if (preediting)
667         {
668           FREE_PREEDIT_BUFFER (context_ime);
669
670           context_ime->priv->comp_str_len
671             = ImmGetCompositionStringW (himc, GCS_COMPSTR, NULL, 0);
672           context_ime->priv->comp_str
673             = g_malloc (context_ime->priv->comp_str_len);
674           ImmGetCompositionStringW (himc, GCS_COMPSTR,
675                                     context_ime->priv->comp_str,
676                                     context_ime->priv->comp_str_len);
677
678           context_ime->priv->read_str_len
679             = ImmGetCompositionStringW (himc, GCS_COMPREADSTR, NULL, 0);
680           context_ime->priv->read_str
681             = g_malloc (context_ime->priv->read_str_len);
682           ImmGetCompositionStringW (himc, GCS_COMPREADSTR,
683                                     context_ime->priv->read_str,
684                                     context_ime->priv->read_str_len);
685         }
686
687       ImmSetOpenStatus (himc, FALSE);
688
689       context_ime->opened = TRUE;
690       context_ime->preediting = preediting;
691     }
692   else
693     {
694       context_ime->opened = FALSE;
695       context_ime->preediting = FALSE;
696     }
697
698   /* remove signal handler */
699   gdk_window_get_user_data (context_ime->client_window, (gpointer) & widget);
700   if (GTK_IS_WIDGET (widget))
701     {
702       g_signal_handlers_disconnect_by_func
703         (G_OBJECT (widget),
704          G_CALLBACK (cb_client_widget_hierarchy_changed), context_ime);
705     }
706
707   /* remove event fileter */
708   toplevel = gdk_window_get_toplevel (context_ime->client_window);
709   if (GDK_IS_WINDOW (toplevel))
710     {
711       gdk_window_remove_filter (toplevel,
712                                 gtk_im_context_ime_message_filter,
713                                 context_ime);
714       top_hwnd = GDK_WINDOW_HWND (toplevel);
715
716       context_ime->toplevel = NULL;
717     }
718   else
719     {
720       g_warning ("gtk_im_context_ime_focus_out(): "
721                  "cannot find toplevel window.");
722     }
723
724   /* clean */
725   ImmReleaseContext (hwnd, himc);
726 }
727
728
729 static void
730 gtk_im_context_ime_set_cursor_location (GtkIMContext *context,
731                                         GdkRectangle *area)
732 {
733   gint wx = 0, wy = 0;
734   GtkIMContextIME *context_ime;
735   COMPOSITIONFORM cf;
736   HWND hwnd;
737   HIMC himc;
738
739   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
740
741   context_ime = GTK_IM_CONTEXT_IME (context);
742   if (area)
743     context_ime->cursor_location = *area;
744
745   if (!context_ime->client_window)
746     return;
747
748   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
749   himc = ImmGetContext (hwnd);
750   if (!himc)
751     return;
752
753   get_window_position (context_ime->client_window, &wx, &wy);
754   cf.dwStyle = CFS_POINT;
755   cf.ptCurrentPos.x = wx + context_ime->cursor_location.x;
756   cf.ptCurrentPos.y = wy + context_ime->cursor_location.y;
757   ImmSetCompositionWindow (himc, &cf);
758
759   ImmReleaseContext (hwnd, himc);
760 }
761
762
763 static void
764 gtk_im_context_ime_set_use_preedit (GtkIMContext *context,
765                                     gboolean      use_preedit)
766 {
767   GtkIMContextIME *context_ime;
768
769   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
770   context_ime = GTK_IM_CONTEXT_IME (context);
771
772   context_ime->use_preedit = use_preedit;
773   if (context_ime->preediting)
774     {
775       HWND hwnd;
776       HIMC himc;
777
778       hwnd = GDK_WINDOW_HWND (context_ime->client_window);
779       himc = ImmGetContext (hwnd);
780       if (!himc)
781         return;
782
783       /* FIXME: What to do? */
784
785       ImmReleaseContext (hwnd, himc);
786     }
787 }
788
789
790 static void
791 gtk_im_context_ime_set_preedit_font (GtkIMContext *context)
792 {
793   GtkIMContextIME *context_ime;
794   GtkWidget *widget = NULL;
795   HWND hwnd;
796   HIMC himc;
797   HKL ime = GetKeyboardLayout (0);
798   const gchar *lang;
799   gunichar wc;
800   PangoContext *pango_context;
801   PangoFont *font;
802   LOGFONT *logfont;
803
804   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context));
805
806   context_ime = GTK_IM_CONTEXT_IME (context);
807   if (!context_ime->client_window)
808     return;
809
810   gdk_window_get_user_data (context_ime->client_window, (gpointer) &widget);
811   if (!GTK_IS_WIDGET (widget))
812     return;
813
814   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
815   himc = ImmGetContext (hwnd);
816   if (!himc)
817     return;
818
819   /* set font */
820   pango_context = gtk_widget_get_pango_context (widget);
821   if (!pango_context)
822     goto ERROR_OUT;
823
824   /* Try to make sure we use a font that actually can show the
825    * language in question.
826    */ 
827
828   switch (PRIMARYLANGID (LOWORD (ime)))
829     {
830     case LANG_JAPANESE:
831       lang = "ja"; break;
832     case LANG_KOREAN:
833       lang = "ko"; break;
834     case LANG_CHINESE:
835       switch (SUBLANGID (LOWORD (ime)))
836         {
837         case SUBLANG_CHINESE_TRADITIONAL:
838           lang = "zh_TW"; break;
839         case SUBLANG_CHINESE_SIMPLIFIED:
840           lang = "zh_CN"; break;
841         case SUBLANG_CHINESE_HONGKONG:
842           lang = "zh_HK"; break;
843         case SUBLANG_CHINESE_SINGAPORE:
844           lang = "zh_SG"; break;
845         case SUBLANG_CHINESE_MACAU:
846           lang = "zh_MO"; break;
847         default:
848           lang = "zh"; break;
849         }
850       break;
851     default:
852       lang = ""; break;
853     }
854   
855   if (lang[0])
856     {
857       /* We know what language it is. Look for a character, any
858        * character, that language needs.
859        */
860       PangoLanguage *pango_lang = pango_language_from_string (lang);
861       PangoFontset *fontset =
862         pango_context_load_fontset (pango_context,
863                                     gtk_widget_get_style (widget)->font_desc,
864                                     pango_lang);
865       gunichar *sample =
866         g_utf8_to_ucs4 (pango_language_get_sample_string (pango_lang),
867                         -1, NULL, NULL, NULL);
868       wc = 0x4E00;              /* In all CJK languages? */
869       if (sample != NULL)
870         {
871           int i;
872
873           for (i = 0; sample[i]; i++)
874             if (g_unichar_iswide (sample[i]))
875               {
876                 wc = sample[i];
877                 break;
878               }
879           g_free (sample);
880         }
881       font = pango_fontset_get_font (fontset, wc);
882       g_object_unref (fontset);
883     }
884   else
885     font = pango_context_load_font (pango_context, gtk_widget_get_style (widget)->font_desc);
886
887   if (!font)
888     goto ERROR_OUT;
889
890   logfont = pango_win32_font_logfont (font);
891   if (logfont)
892     ImmSetCompositionFont (himc, logfont);
893
894   g_object_unref (font);
895
896 ERROR_OUT:
897   /* clean */
898   ImmReleaseContext (hwnd, himc);
899 }
900
901
902 static GdkFilterReturn
903 gtk_im_context_ime_message_filter (GdkXEvent *xevent,
904                                    GdkEvent  *event,
905                                    gpointer   data)
906 {
907   GtkIMContext *context;
908   GtkIMContextIME *context_ime;
909   HWND hwnd;
910   HIMC himc;
911   MSG *msg = (MSG *) xevent;
912   GdkFilterReturn retval = GDK_FILTER_CONTINUE;
913
914   g_return_val_if_fail (GTK_IS_IM_CONTEXT_IME (data), retval);
915
916   context = GTK_IM_CONTEXT (data);
917   context_ime = GTK_IM_CONTEXT_IME (data);
918   if (!context_ime->focus)
919     return retval;
920
921   hwnd = GDK_WINDOW_HWND (context_ime->client_window);
922   himc = ImmGetContext (hwnd);
923   if (!himc)
924     return retval;
925
926   switch (msg->message)
927     {
928     case WM_IME_COMPOSITION:
929       {
930         gint wx = 0, wy = 0;
931         CANDIDATEFORM cf;
932
933         get_window_position (context_ime->client_window, &wx, &wy);
934         /* FIXME! */
935         {
936           HWND hwnd_top;
937           POINT pt;
938           RECT rc;
939
940           hwnd_top =
941             GDK_WINDOW_HWND (gdk_window_get_toplevel
942                              (context_ime->client_window));
943           GetWindowRect (hwnd_top, &rc);
944           pt.x = wx;
945           pt.y = wy;
946           ClientToScreen (hwnd_top, &pt);
947           wx = pt.x - rc.left;
948           wy = pt.y - rc.top;
949         }
950         cf.dwIndex = 0;
951         cf.dwStyle = CFS_CANDIDATEPOS;
952         cf.ptCurrentPos.x = wx + context_ime->cursor_location.x;
953         cf.ptCurrentPos.y = wy + context_ime->cursor_location.y
954           + context_ime->cursor_location.height;
955         ImmSetCandidateWindow (himc, &cf);
956
957         if ((msg->lParam & GCS_COMPSTR))
958           g_signal_emit_by_name (context, "preedit-changed");
959
960         if (msg->lParam & GCS_RESULTSTR)
961           {
962             gsize len;
963             gchar *utf8str = NULL;
964             GError *error = NULL;
965
966             len = ImmGetCompositionStringW (himc, GCS_RESULTSTR, NULL, 0);
967
968             if (len > 0)
969               {
970                 gpointer buf = g_alloca (len);
971                 ImmGetCompositionStringW (himc, GCS_RESULTSTR, buf, len);
972                 len /= 2;
973                 utf8str = g_utf16_to_utf8 (buf, len, NULL, NULL, &error);
974                 if (error)
975                   {
976                     g_warning ("%s", error->message);
977                     g_error_free (error);
978                   }
979               }
980
981             if (utf8str)
982               {
983                 g_signal_emit_by_name (context, "commit", utf8str);
984                 g_free (utf8str);
985                 retval = TRUE;
986               }
987           }
988
989         if (context_ime->use_preedit)
990           retval = TRUE;
991         break;
992       }
993
994     case WM_IME_STARTCOMPOSITION:
995       context_ime->preediting = TRUE;
996       gtk_im_context_ime_set_cursor_location (context, NULL);
997       g_signal_emit_by_name (context, "preedit-start");
998       if (context_ime->use_preedit)
999         retval = TRUE;
1000       break;
1001
1002     case WM_IME_ENDCOMPOSITION:
1003       context_ime->preediting = FALSE;
1004       g_signal_emit_by_name (context, "preedit-changed");
1005       g_signal_emit_by_name (context, "preedit-end");
1006       if (context_ime->use_preedit)
1007         retval = TRUE;
1008       break;
1009
1010     case WM_IME_NOTIFY:
1011       switch (msg->wParam)
1012         {
1013         case IMN_SETOPENSTATUS:
1014           context_ime->opened = ImmGetOpenStatus (himc);
1015           gtk_im_context_ime_set_preedit_font (context);
1016           break;
1017
1018         default:
1019           break;
1020         }
1021
1022     default:
1023       break;
1024     }
1025
1026   ImmReleaseContext (hwnd, himc);
1027   return retval;
1028 }
1029
1030
1031 /*
1032  * x and y must be initialized to 0.
1033  */
1034 static void
1035 get_window_position (GdkWindow *win, gint *x, gint *y)
1036 {
1037   GdkWindow *parent, *toplevel;
1038   gint wx, wy;
1039
1040   g_return_if_fail (GDK_IS_WINDOW (win));
1041   g_return_if_fail (x && y);
1042
1043   gdk_window_get_position (win, &wx, &wy);
1044   *x += wx;
1045   *y += wy;
1046   parent = gdk_window_get_parent (win);
1047   toplevel = gdk_window_get_toplevel (win);
1048
1049   if (parent && parent != toplevel)
1050     get_window_position (parent, x, y);
1051 }
1052
1053
1054 /*
1055  *  probably, this handler isn't needed.
1056  */
1057 static void
1058 cb_client_widget_hierarchy_changed (GtkWidget       *widget,
1059                                     GtkWidget       *widget2,
1060                                     GtkIMContextIME *context_ime)
1061 {
1062   GdkWindow *new_toplevel;
1063
1064   g_return_if_fail (GTK_IS_WIDGET (widget));
1065   g_return_if_fail (GTK_IS_IM_CONTEXT_IME (context_ime));
1066
1067   if (!context_ime->client_window)
1068     return;
1069   if (!context_ime->focus)
1070     return;
1071
1072   new_toplevel = gdk_window_get_toplevel (context_ime->client_window);
1073   if (context_ime->toplevel == new_toplevel)
1074     return;
1075
1076   /* remove filter from old toplevel */
1077   if (GDK_IS_WINDOW (context_ime->toplevel))
1078     {
1079       gdk_window_remove_filter (context_ime->toplevel,
1080                                 gtk_im_context_ime_message_filter,
1081                                 context_ime);
1082     }
1083   else
1084     {
1085     }
1086
1087   /* add filter to new toplevel */
1088   if (GDK_IS_WINDOW (new_toplevel))
1089     {
1090       gdk_window_add_filter (new_toplevel,
1091                              gtk_im_context_ime_message_filter, context_ime);
1092     }
1093   else
1094     {
1095     }
1096
1097   context_ime->toplevel = new_toplevel;
1098 }