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_focus_in (GtkIMContext *context);
42 static void gtk_im_context_xim_focus_out (GtkIMContext *context);
43 static void gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
45 static void gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
47 PangoAttrList **attrs,
50 static XIC gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim);
51 static GObjectClass *parent_class;
53 GType gtk_type_im_context_xim = 0;
55 static GSList *open_ims = NULL;
58 gtk_im_context_xim_register_type (GTypeModule *type_module)
60 static const GTypeInfo im_context_xim_info =
62 sizeof (GtkIMContextXIMClass),
64 (GBaseFinalizeFunc) NULL,
65 (GClassInitFunc) gtk_im_context_xim_class_init,
66 NULL, /* class_finalize */
67 NULL, /* class_data */
68 sizeof (GtkIMContextXIM),
70 (GtkObjectInitFunc) gtk_im_context_xim_init,
73 gtk_type_im_context_xim =
74 g_type_module_register_type (type_module,
77 &im_context_xim_info, 0);
80 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
81 XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
82 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
83 XIMStatusNothing | XIMStatusNone)
84 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
85 XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
88 choose_better_style (XIMStyle style1, XIMStyle style2)
92 if (style1 == 0) return style2;
93 if (style2 == 0) return style1;
94 if ((style1 & (PREEDIT_MASK | STATUS_MASK))
95 == (style2 & (PREEDIT_MASK | STATUS_MASK)))
98 s1 = style1 & PREEDIT_MASK;
99 s2 = style2 & PREEDIT_MASK;
102 if (u & XIMPreeditCallbacks)
103 return (s1 == XIMPreeditCallbacks) ? style1 : style2;
104 else if (u & XIMPreeditPosition)
105 return (s1 == XIMPreeditPosition) ? style1 :style2;
106 else if (u & XIMPreeditArea)
107 return (s1 == XIMPreeditArea) ? style1 : style2;
108 else if (u & XIMPreeditNothing)
109 return (s1 == XIMPreeditNothing) ? style1 : style2;
111 s1 = style1 & STATUS_MASK;
112 s2 = style2 & STATUS_MASK;
114 if (u & XIMStatusCallbacks)
115 return (s1 == XIMStatusCallbacks) ? style1 : style2;
116 else if (u & XIMStatusArea)
117 return (s1 == XIMStatusArea) ? style1 : style2;
118 else if (u & XIMStatusNothing)
119 return (s1 == XIMStatusNothing) ? style1 : style2;
120 else if (u & XIMStatusNone)
121 return (s1 == XIMStatusNone) ? style1 : style2;
123 return 0; /* Get rid of stupid warning */
127 setup_im (GtkXIMInfo *info)
129 XIMStyles *xim_styles = NULL;
130 XIMValuesList *ic_values = NULL;
133 XGetIMValues (info->im,
134 XNQueryInputStyle, &xim_styles,
135 XNQueryICValuesList, &ic_values,
141 for (i = 0; i < xim_styles->count_styles; i++)
142 if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
143 info->style = choose_better_style (info->style,
144 xim_styles->supported_styles[i]);
151 for (i = 0; i < ic_values->count_values; i++)
152 g_print ("%s\n", ic_values->supported_values[i]);
153 for (i = 0; i < xim_styles->count_styles; i++)
154 g_print ("%#x\n", xim_styles->supported_styles[i]);
165 get_im (const char *locale)
167 GSList *tmp_list = open_ims;
173 info = tmp_list->data;
174 if (!strcmp (info->locale, locale))
177 tmp_list = tmp_list->next;
182 if (XSupportsLocale ())
184 if (!XSetLocaleModifiers (""))
185 g_warning ("can not set locale modifiers");
187 im = XOpenIM (GDK_DISPLAY(), NULL, NULL, NULL);
191 info = g_new (GtkXIMInfo, 1);
192 open_ims = g_slist_prepend (open_ims, im);
194 info->locale = g_strdup (locale);
205 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
207 GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
208 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
210 parent_class = g_type_class_peek_parent (class);
212 im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
213 im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
214 im_context_class->reset = gtk_im_context_xim_reset;
215 im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
216 im_context_class->focus_in = gtk_im_context_xim_focus_in;
217 im_context_class->focus_out = gtk_im_context_xim_focus_out;
218 im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
219 gobject_class->finalize = gtk_im_context_xim_finalize;
223 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
228 gtk_im_context_xim_finalize (GObject *obj)
230 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
234 XDestroyIC (context_xim->ic);
235 context_xim->ic = NULL;
238 g_free (context_xim->mb_charset);
242 gtk_im_context_xim_set_client_window (GtkIMContext *context,
243 GdkWindow *client_window)
245 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
249 XDestroyIC (context_xim->ic);
250 context_xim->ic = NULL;
253 context_xim->client_window = client_window;
257 gtk_im_context_xim_new (void)
260 GtkIMContextXIM *result;
261 const gchar *charset;
263 info = get_im (setlocale (LC_CTYPE, NULL));
267 result = GTK_IM_CONTEXT_XIM (gtk_type_new (GTK_TYPE_IM_CONTEXT_XIM));
269 result->im_info = info;
271 g_get_charset (&charset);
272 result->mb_charset = g_strdup (charset);
274 return GTK_IM_CONTEXT (result);
278 mb_to_utf8 (GtkIMContextXIM *context_xim,
281 GError *error = NULL;
284 if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
285 result = g_strdup (str);
288 result = g_convert (str, -1,
289 "UTF-8", context_xim->mb_charset,
293 g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
294 g_error_free (error);
302 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
305 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
306 XIC ic = gtk_im_context_xim_get_ic (context_xim);
307 gchar static_buffer[256];
308 gchar *buffer = static_buffer;
309 gint buffer_size = sizeof(static_buffer) - 1;
313 gboolean result = FALSE;
315 XKeyPressedEvent xevent;
320 xevent.type = KeyPress;
321 xevent.serial = 0; /* hope it doesn't matter */
322 xevent.send_event = event->send_event;
323 xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
324 xevent.window = GDK_DRAWABLE_XID (event->window);
325 xevent.root = GDK_ROOT_WINDOW();
326 xevent.subwindow = xevent.window;
327 xevent.time = event->time;
328 xevent.x = xevent.x_root = 0;
329 xevent.y = xevent.y_root = 0;
330 xevent.state = event->state;
331 xevent.keycode = event->keyval ? XKeysymToKeycode (xevent.display, event->keyval) : 0;
332 xevent.same_screen = True;
334 if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
338 num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
340 if (status == XBufferOverflow)
342 buffer_size = num_bytes;
343 buffer = g_malloc (num_bytes + 1);
347 /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
348 * here ... do input methods actually change the keysym? we can't really
349 * feed it back to accelerator processing at this point...
351 if (status == XLookupChars || status == XLookupBoth)
355 buffer[num_bytes] = '\0';
357 result_utf8 = mb_to_utf8 (context_xim, buffer);
360 if ((guchar)result_utf8[0] >= 0x20) /* Some IM have a nasty habit of converting
361 * control characters into strings
364 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
368 g_free (result_utf8);
376 gtk_im_context_xim_focus_in (GtkIMContext *context)
378 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
379 XIC ic = gtk_im_context_xim_get_ic (context_xim);
389 gtk_im_context_xim_focus_out (GtkIMContext *context)
391 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
392 XIC ic = gtk_im_context_xim_get_ic (context_xim);
402 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
405 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
406 XIC ic = gtk_im_context_xim_get_ic (context_xim);
408 XVaNestedList preedit_attr;
417 preedit_attr = XVaCreateNestedList (0,
418 XNSpotLocation, &spot,
421 XNPreeditAttributes, preedit_attr,
429 gtk_im_context_xim_reset (GtkIMContext *context)
431 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
432 XIC ic = gtk_im_context_xim_get_ic (context_xim);
435 /* restore conversion state after resetting ic later */
436 XIMPreeditState preedit_state = XIMPreeditUnKnown;
437 XVaNestedList preedit_attr;
438 gboolean have_preedit_state = FALSE;
444 preedit_attr = XVaCreateNestedList(0,
445 XNPreeditState, &preedit_state,
447 if (!XGetICValues(ic,
448 XNPreeditAttributes, preedit_attr,
450 have_preedit_state = TRUE;
454 result = XmbResetIC (ic);
456 preedit_attr = XVaCreateNestedList(0,
457 XNPreeditState, preedit_state,
459 if (have_preedit_state)
461 XNPreeditAttributes, preedit_attr,
468 char *result_utf8 = mb_to_utf8 (context_xim, result);
471 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
472 g_free (result_utf8);
476 if (context_xim->preedit_length)
478 context_xim->preedit_length = 0;
479 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
485 /* Mask of feedback bits that we render
487 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
490 add_feedback_attr (PangoAttrList *attrs,
492 XIMFeedback feedback,
496 PangoAttribute *attr;
498 gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
499 gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
501 if (feedback & XIMUnderline)
503 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
504 attr->start_index = start_index;
505 attr->end_index = end_index;
507 pango_attr_list_change (attrs, attr);
510 if (feedback & XIMReverse)
512 attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
513 attr->start_index = start_index;
514 attr->end_index = end_index;
516 pango_attr_list_change (attrs, attr);
518 attr = pango_attr_background_new (0, 0, 0);
519 attr->start_index = start_index;
520 attr->end_index = end_index;
522 pango_attr_list_change (attrs, attr);
525 if (feedback & ~FEEDBACK_MASK)
526 g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
530 gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
532 PangoAttrList **attrs,
535 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
536 gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
541 XIMFeedback last_feedback = 0;
544 *attrs = pango_attr_list_new ();
546 for (i = 0; i < context_xim->preedit_length; i++)
548 XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
549 if (new_feedback != last_feedback)
552 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
554 last_feedback = new_feedback;
560 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
569 *cursor_pos = context_xim->preedit_cursor;
573 preedit_start_callback (XIC xic,
574 XPointer client_data,
577 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
579 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_start");
580 g_print ("Starting preedit!\n");
584 preedit_done_callback (XIC xic,
585 XPointer client_data,
588 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
590 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_end");
591 g_print ("Ending preedit!\n");
595 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
597 gint text_length = 0;
598 GError *error = NULL;
599 gchar *result = NULL;
601 if (xim_text && xim_text->string.multi_byte)
603 if (xim_text->encoding_is_wchar)
605 g_warning ("Wide character return from Xlib not currently supported");
610 if (strcmp (context->mb_charset, "UTF-8") == 0)
611 result = g_strdup (xim_text->string.multi_byte);
613 result = g_convert (xim_text->string.multi_byte,
621 text_length = g_utf8_strlen (result, -1);
623 if (text_length != xim_text->length)
625 g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
630 g_warning ("Error converting text from IM to UCS-4: %s", error->message);
631 g_error_free (error);
648 preedit_draw_callback (XIC xic,
649 XPointer client_data,
650 XIMPreeditDrawCallbackStruct *call_data)
652 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
654 XIMText *new_xim_text = call_data->text;
655 gint new_text_length;
656 gunichar *new_text = NULL;
662 gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
663 gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
665 context->preedit_cursor = call_data->caret;
667 if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
668 g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
669 call_data->chg_first, call_data->chg_length, context->preedit_length);
671 new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
674 new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
678 diff = new_text_length - chg_length;
679 new_length = context->preedit_length + diff;
681 if (new_length > context->preedit_size)
683 context->preedit_size = new_length;
684 context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
685 context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
690 for (i = chg_first + chg_length ; i < context->preedit_length; i++)
692 context->preedit_chars[i + diff] = context->preedit_chars[i];
693 context->feedbacks[i + diff] = context->feedbacks[i];
698 for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
700 context->preedit_chars[i + diff] = context->preedit_chars[i];
701 context->feedbacks[i + diff] = context->feedbacks[i];
705 for (i = 0; i < new_text_length; i++)
707 context->preedit_chars[chg_first + i] = new_text[i];
708 context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
711 context->preedit_length += diff;
716 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
721 preedit_caret_callback (XIC xic,
722 XPointer client_data,
723 XIMPreeditCaretCallbackStruct *call_data)
725 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
727 if (call_data->direction == XIMAbsolutePosition)
729 context->preedit_cursor = call_data->position;
730 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
734 g_warning ("Caret movement command: %d %d %d not supported",
735 call_data->position, call_data->direction, call_data->style);
740 status_start_callback (XIC xic,
741 XPointer client_data,
744 g_print ("Status start\n");
748 status_done_callback (XIC xic,
749 XPointer client_data,
752 g_print ("Status done!\n");
756 status_draw_callback (XIC xic,
757 XPointer client_data,
758 XIMStatusDrawCallbackStruct *call_data)
760 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
762 g_print ("Status draw\n");
763 if (call_data->type == XIMTextType)
766 xim_text_to_utf8 (context, call_data->data.text, &text);
769 g_print (" %s\n", text);
773 g_print (" bitmap id = %#lx\n", call_data->data.bitmap);
778 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
780 const char *name1 = NULL;
781 XVaNestedList list1 = NULL;
782 const char *name2 = NULL;
783 XVaNestedList list2 = NULL;
785 if (!context_xim->ic && context_xim->client_window)
787 if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
789 context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
790 context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
791 context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
792 context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
793 context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
794 context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
795 context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
796 context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
798 name1 = XNPreeditAttributes;
799 list1 = XVaCreateNestedList (0,
800 XNPreeditStartCallback, &context_xim->preedit_start_callback,
801 XNPreeditDoneCallback, &context_xim->preedit_done_callback,
802 XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
803 XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
807 if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
809 XVaNestedList status_attrs;
811 context_xim->status_start_callback.client_data = (XPointer)context_xim;
812 context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
813 context_xim->status_done_callback.client_data = (XPointer)context_xim;
814 context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
815 context_xim->status_draw_callback.client_data = (XPointer)context_xim;
816 context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
818 status_attrs = XVaCreateNestedList (0,
819 XNStatusStartCallback, &context_xim->status_start_callback,
820 XNStatusDoneCallback, &context_xim->status_done_callback,
821 XNStatusDrawCallback, &context_xim->status_draw_callback,
826 name1 = XNStatusAttributes;
827 list1 = status_attrs;
831 name2 = XNStatusAttributes;
832 list2 = status_attrs;
836 context_xim->ic = XCreateIC (context_xim->im_info->im,
837 XNInputStyle, context_xim->im_info->style,
838 XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
849 return context_xim->ic;