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