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