1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2000 Red Hat, Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
23 #include "gtk/gtksignal.h"
24 #include "gtkimcontextxim.h"
33 static void gtk_im_context_xim_class_init (GtkIMContextXIMClass *class);
34 static void gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim);
35 static void gtk_im_context_xim_finalize (GObject *obj);
36 static void gtk_im_context_xim_set_client_window (GtkIMContext *context,
37 GdkWindow *client_window);
38 static gboolean gtk_im_context_xim_filter_keypress (GtkIMContext *context,
40 static void gtk_im_context_xim_reset (GtkIMContext *context);
41 static void gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
43 PangoAttrList **attrs,
46 static XIC gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim);
47 static GObjectClass *parent_class;
49 GType gtk_type_im_context_xim = 0;
51 static GSList *open_ims = NULL;
54 gtk_im_context_xim_register_type (GTypeModule *type_module)
56 static const GTypeInfo im_context_xim_info =
58 sizeof (GtkIMContextXIMClass),
60 (GBaseFinalizeFunc) NULL,
61 (GClassInitFunc) gtk_im_context_xim_class_init,
62 NULL, /* class_finalize */
63 NULL, /* class_data */
64 sizeof (GtkIMContextXIM),
66 (GtkObjectInitFunc) gtk_im_context_xim_init,
69 gtk_type_im_context_xim =
70 g_type_module_register_type (type_module,
73 &im_context_xim_info, 0);
76 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
77 XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
78 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
79 XIMStatusNothing | XIMStatusNone)
80 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
81 XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
84 choose_better_style (XIMStyle style1, XIMStyle style2)
88 if (style1 == 0) return style2;
89 if (style2 == 0) return style1;
90 if ((style1 & (PREEDIT_MASK | STATUS_MASK))
91 == (style2 & (PREEDIT_MASK | STATUS_MASK)))
94 s1 = style1 & PREEDIT_MASK;
95 s2 = style2 & PREEDIT_MASK;
98 if (u & XIMPreeditCallbacks)
99 return (s1 == XIMPreeditCallbacks) ? style1 : style2;
100 else if (u & XIMPreeditPosition)
101 return (s1 == XIMPreeditPosition) ? style1 :style2;
102 else if (u & XIMPreeditArea)
103 return (s1 == XIMPreeditArea) ? style1 : style2;
104 else if (u & XIMPreeditNothing)
105 return (s1 == XIMPreeditNothing) ? style1 : style2;
107 s1 = style1 & STATUS_MASK;
108 s2 = style2 & STATUS_MASK;
110 if (u & XIMStatusCallbacks)
111 return (s1 == XIMStatusCallbacks) ? style1 : style2;
112 else if (u & XIMStatusArea)
113 return (s1 == XIMStatusArea) ? style1 : style2;
114 else if (u & XIMStatusNothing)
115 return (s1 == XIMStatusNothing) ? style1 : style2;
116 else if (u & XIMStatusNone)
117 return (s1 == XIMStatusNone) ? style1 : style2;
119 return 0; /* Get rid of stupid warning */
123 setup_im (GtkXIMInfo *info)
125 XIMStyles *xim_styles = NULL;
126 XIMValuesList *ic_values = NULL;
129 XGetIMValues (info->im,
130 XNQueryInputStyle, &xim_styles,
131 XNQueryICValuesList, &ic_values,
137 for (i = 0; i < xim_styles->count_styles; i++)
138 if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
139 info->style = choose_better_style (info->style,
140 xim_styles->supported_styles[i]);
147 for (i = 0; i < ic_values->count_values; i++)
148 g_print ("%s\n", ic_values->supported_values[i]);
149 for (i = 0; i < xim_styles->count_styles; i++)
150 g_print ("%#x\n", xim_styles->supported_styles[i]);
161 get_im (const char *locale)
163 GSList *tmp_list = open_ims;
169 info = tmp_list->data;
170 if (!strcmp (info->locale, locale))
173 tmp_list = tmp_list->next;
178 if (XSupportsLocale ())
180 if (!XSetLocaleModifiers (""))
181 g_warning ("can not set locale modifiers");
183 im = XOpenIM (GDK_DISPLAY(), NULL, NULL, NULL);
187 info = g_new (GtkXIMInfo, 1);
188 open_ims = g_slist_prepend (open_ims, im);
190 info->locale = g_strdup (locale);
201 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
203 GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
204 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
206 parent_class = g_type_class_peek_parent (class);
208 im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
209 im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
210 im_context_class->reset = gtk_im_context_xim_reset;
211 im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
212 gobject_class->finalize = gtk_im_context_xim_finalize;
216 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
221 gtk_im_context_xim_finalize (GObject *obj)
223 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
227 XDestroyIC (context_xim->ic);
228 context_xim->ic = NULL;
231 g_free (context_xim->mb_charset);
235 gtk_im_context_xim_set_client_window (GtkIMContext *context,
236 GdkWindow *client_window)
238 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
242 XDestroyIC (context_xim->ic);
243 context_xim->ic = NULL;
246 context_xim->client_window = client_window;
250 gtk_im_context_xim_new (void)
253 GtkIMContextXIM *result;
256 info = get_im (setlocale (LC_CTYPE, NULL));
260 result = GTK_IM_CONTEXT_XIM (gtk_type_new (GTK_TYPE_IM_CONTEXT_XIM));
262 result->im_info = info;
264 g_get_charset (&charset);
265 result->mb_charset = g_strdup (charset);
267 return GTK_IM_CONTEXT (result);
271 mb_to_utf8 (GtkIMContextXIM *context_xim,
274 GError *error = NULL;
277 result = g_convert (str, -1,
278 "UTF-8", context_xim->mb_charset,
283 g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
284 g_error_free (error);
291 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
294 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
295 XIC ic = gtk_im_context_xim_get_ic (context_xim);
296 gchar static_buffer[256];
297 gchar *buffer = static_buffer;
298 gint buffer_size = sizeof(static_buffer) - 1;
302 gboolean result = FALSE;
304 XKeyPressedEvent xevent;
309 xevent.type = KeyPress;
310 xevent.serial = 0; /* hope it doesn't matter */
311 xevent.send_event = event->send_event;
312 xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
313 xevent.window = GDK_DRAWABLE_XID (event->window);
314 xevent.root = GDK_ROOT_WINDOW();
315 xevent.subwindow = xevent.window;
316 xevent.time = event->time;
317 xevent.x = xevent.x_root = 0;
318 xevent.y = xevent.y_root = 0;
319 xevent.state = event->state;
320 xevent.keycode = event->keyval ? XKeysymToKeycode (xevent.display, event->keyval) : 0;
321 xevent.same_screen = True;
323 if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
327 num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
329 if (status == XBufferOverflow)
331 buffer_size = num_bytes;
332 buffer = g_malloc (num_bytes + 1);
336 /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
337 * here ... do input methods actually change the keysym? we can't really
338 * feed it back to accelerator processing at this point...
340 if (status == XLookupChars || status == XLookupBoth)
344 buffer[num_bytes] = '\0';
346 result_utf8 = mb_to_utf8 (context_xim, buffer);
349 if ((guchar)result_utf8[0] >= 0x20) /* Some IM have a nasty habit of converting
350 * control characters into strings
353 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
357 g_free (result_utf8);
365 gtk_im_context_xim_reset (GtkIMContext *context)
367 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
368 XIC ic = gtk_im_context_xim_get_ic (context_xim);
371 /* restore conversion state after resetting ic later */
372 XIMPreeditState preedit_state = XIMPreeditUnKnown;
373 XVaNestedList preedit_attr;
374 gboolean have_preedit_state = FALSE;
380 preedit_attr = XVaCreateNestedList(0,
381 XNPreeditState, &preedit_state,
383 if (!XGetICValues(ic,
384 XNPreeditAttributes, preedit_attr,
386 have_preedit_state = TRUE;
390 result = XmbResetIC (ic);
392 preedit_attr = XVaCreateNestedList(0,
393 XNPreeditState, preedit_state,
395 if (have_preedit_state)
397 XNPreeditAttributes, preedit_attr,
404 char *result_utf8 = mb_to_utf8 (context_xim, result);
407 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
408 g_free (result_utf8);
412 if (context_xim->preedit_length)
414 context_xim->preedit_length = 0;
415 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
421 /* Mask of feedback bits that we render
423 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
426 add_feedback_attr (PangoAttrList *attrs,
428 XIMFeedback feedback,
432 PangoAttribute *attr;
434 gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
435 gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
437 if (feedback & XIMUnderline)
439 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
440 attr->start_index = start_index;
441 attr->end_index = end_index;
443 pango_attr_list_change (attrs, attr);
446 if (feedback & XIMReverse)
448 attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
449 attr->start_index = start_index;
450 attr->end_index = end_index;
452 pango_attr_list_change (attrs, attr);
454 attr = pango_attr_background_new (0, 0, 0);
455 attr->start_index = start_index;
456 attr->end_index = end_index;
458 pango_attr_list_change (attrs, attr);
461 if (feedback & ~FEEDBACK_MASK)
462 g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
466 gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
468 PangoAttrList **attrs,
471 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
472 gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length);
477 XIMFeedback last_feedback = 0;
480 *attrs = pango_attr_list_new ();
482 for (i = 0; i < context_xim->preedit_length; i++)
484 XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
485 if (new_feedback != last_feedback)
488 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
490 last_feedback = new_feedback;
496 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
505 *cursor_pos = context_xim->preedit_cursor;
509 preedit_start_callback (XIC xic,
510 XPointer client_data,
513 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
515 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_start");
516 g_print ("Starting preedit!\n");
520 preedit_done_callback (XIC xic,
521 XPointer client_data,
524 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
526 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_end");
527 g_print ("Ending preedit!\n");
531 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
533 gint text_length = 0;
534 GError *error = NULL;
535 gchar *result = NULL;
537 if (xim_text && xim_text->string.multi_byte)
539 if (xim_text->encoding_is_wchar)
541 g_warning ("Wide character return from Xlib not currently supported");
546 result = g_convert (xim_text->string.multi_byte,
550 NULL, &text_length, &error);
554 text_length = g_utf8_strlen (result, -1);
556 if (text_length != xim_text->length)
558 g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
563 g_warning ("Error converting text from IM to UCS-4: %s", error->message);
564 g_error_free (error);
578 preedit_draw_callback (XIC xic,
579 XPointer client_data,
580 XIMPreeditDrawCallbackStruct *call_data)
582 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
584 XIMText *new_xim_text = call_data->text;
585 gint new_text_length;
586 gunichar *new_text = NULL;
592 gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
593 gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
595 context->preedit_cursor = call_data->caret;
597 if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
598 g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
599 call_data->chg_first, call_data->chg_length, context->preedit_length);
601 new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
604 new_text = g_utf8_to_ucs4 (tmp, -1);
608 diff = new_text_length - chg_length;
609 new_length = context->preedit_length + diff;
611 if (new_length > context->preedit_size)
613 context->preedit_size = new_length;
614 context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
615 context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
620 for (i = chg_first + chg_length ; i < context->preedit_length; i++)
622 context->preedit_chars[i + diff] = context->preedit_chars[i];
623 context->feedbacks[i + diff] = context->feedbacks[i];
628 for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
630 context->preedit_chars[i + diff] = context->preedit_chars[i];
631 context->feedbacks[i + diff] = context->feedbacks[i];
635 for (i = 0; i < new_text_length; i++)
637 context->preedit_chars[chg_first + i] = new_text[i];
638 context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
641 context->preedit_length += diff;
646 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
651 preedit_caret_callback (XIC xic,
652 XPointer client_data,
653 XIMPreeditCaretCallbackStruct *call_data)
655 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
657 if (call_data->direction == XIMAbsolutePosition)
659 context->preedit_cursor = call_data->position;
660 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
664 g_warning ("Caret movement command: %d %d %d not supported",
665 call_data->position, call_data->direction, call_data->style);
670 status_start_callback (XIC xic,
671 XPointer client_data,
674 g_print ("Status start\n");
678 status_done_callback (XIC xic,
679 XPointer client_data,
682 g_print ("Status done!\n");
686 status_draw_callback (XIC xic,
687 XPointer client_data,
688 XIMStatusDrawCallbackStruct *call_data)
690 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
692 g_print ("Status draw\n");
693 if (call_data->type == XIMTextType)
696 xim_text_to_utf8 (context, call_data->data.text, &text);
699 g_print (" %s\n", text);
703 g_print (" bitmap id = %#lx\n", call_data->data.bitmap);
708 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
710 const char *name1 = NULL;
711 XVaNestedList list1 = NULL;
712 const char *name2 = NULL;
713 XVaNestedList list2 = NULL;
715 if (!context_xim->ic && context_xim->client_window)
717 if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
719 context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
720 context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
721 context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
722 context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
723 context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
724 context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
725 context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
726 context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
728 name1 = XNPreeditAttributes;
729 list1 = XVaCreateNestedList (0,
730 XNPreeditStartCallback, &context_xim->preedit_start_callback,
731 XNPreeditDoneCallback, &context_xim->preedit_done_callback,
732 XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
733 XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
737 if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
739 XVaNestedList status_attrs;
741 context_xim->status_start_callback.client_data = (XPointer)context_xim;
742 context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
743 context_xim->status_done_callback.client_data = (XPointer)context_xim;
744 context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
745 context_xim->status_draw_callback.client_data = (XPointer)context_xim;
746 context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
748 status_attrs = XVaCreateNestedList (0,
749 XNStatusStartCallback, &context_xim->status_start_callback,
750 XNStatusDoneCallback, &context_xim->status_done_callback,
751 XNStatusDrawCallback, &context_xim->status_draw_callback,
756 name1 = XNStatusAttributes;
757 list1 = status_attrs;
761 name2 = XNStatusAttributes;
762 list2 = status_attrs;
766 context_xim->ic = XCreateIC (context_xim->im_info->im,
767 XNInputStyle, context_xim->im_info->style,
768 XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
779 return context_xim->ic;