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