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