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_set_use_preedit (GtkIMContext *context,
46 gboolean use_preedit);
47 static void gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
49 PangoAttrList **attrs,
52 static XIC gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim);
53 static GObjectClass *parent_class;
55 GType gtk_type_im_context_xim = 0;
57 static GSList *open_ims = NULL;
60 gtk_im_context_xim_register_type (GTypeModule *type_module)
62 static const GTypeInfo im_context_xim_info =
64 sizeof (GtkIMContextXIMClass),
66 (GBaseFinalizeFunc) NULL,
67 (GClassInitFunc) gtk_im_context_xim_class_init,
68 NULL, /* class_finalize */
69 NULL, /* class_data */
70 sizeof (GtkIMContextXIM),
72 (GtkObjectInitFunc) gtk_im_context_xim_init,
75 gtk_type_im_context_xim =
76 g_type_module_register_type (type_module,
79 &im_context_xim_info, 0);
82 #define PREEDIT_MASK (XIMPreeditCallbacks | XIMPreeditPosition | \
83 XIMPreeditArea | XIMPreeditNothing | XIMPreeditNone)
84 #define STATUS_MASK (XIMStatusCallbacks | XIMStatusArea | \
85 XIMStatusNothing | XIMStatusNone)
86 #define ALLOWED_MASK (XIMPreeditCallbacks | XIMPreeditNothing | XIMPreeditNone | \
87 XIMStatusCallbacks | XIMStatusNothing | XIMStatusNone)
90 choose_better_style (XIMStyle style1, XIMStyle style2)
94 if (style1 == 0) return style2;
95 if (style2 == 0) return style1;
96 if ((style1 & (PREEDIT_MASK | STATUS_MASK))
97 == (style2 & (PREEDIT_MASK | STATUS_MASK)))
100 s1 = style1 & PREEDIT_MASK;
101 s2 = style2 & PREEDIT_MASK;
104 if (u & XIMPreeditCallbacks)
105 return (s1 == XIMPreeditCallbacks) ? style1 : style2;
106 else if (u & XIMPreeditPosition)
107 return (s1 == XIMPreeditPosition) ? style1 :style2;
108 else if (u & XIMPreeditArea)
109 return (s1 == XIMPreeditArea) ? style1 : style2;
110 else if (u & XIMPreeditNothing)
111 return (s1 == XIMPreeditNothing) ? style1 : style2;
113 s1 = style1 & STATUS_MASK;
114 s2 = style2 & STATUS_MASK;
116 if (u & XIMStatusCallbacks)
117 return (s1 == XIMStatusCallbacks) ? style1 : style2;
118 else if (u & XIMStatusArea)
119 return (s1 == XIMStatusArea) ? style1 : style2;
120 else if (u & XIMStatusNothing)
121 return (s1 == XIMStatusNothing) ? style1 : style2;
122 else if (u & XIMStatusNone)
123 return (s1 == XIMStatusNone) ? style1 : style2;
125 return 0; /* Get rid of stupid warning */
129 setup_im (GtkXIMInfo *info)
131 XIMStyles *xim_styles = NULL;
132 XIMValuesList *ic_values = NULL;
135 XGetIMValues (info->im,
136 XNQueryInputStyle, &xim_styles,
137 XNQueryICValuesList, &ic_values,
143 for (i = 0; i < xim_styles->count_styles; i++)
144 if ((xim_styles->supported_styles[i] & ALLOWED_MASK) == xim_styles->supported_styles[i])
145 info->style = choose_better_style (info->style,
146 xim_styles->supported_styles[i]);
153 for (i = 0; i < ic_values->count_values; i++)
154 g_print ("%s\n", ic_values->supported_values[i]);
155 for (i = 0; i < xim_styles->count_styles; i++)
156 g_print ("%#x\n", xim_styles->supported_styles[i]);
167 get_im (const char *locale)
169 GSList *tmp_list = open_ims;
175 info = tmp_list->data;
176 if (!strcmp (info->locale, locale))
179 tmp_list = tmp_list->next;
184 if (XSupportsLocale ())
186 if (!XSetLocaleModifiers (""))
187 g_warning ("can not set locale modifiers");
189 im = XOpenIM (GDK_DISPLAY(), NULL, NULL, NULL);
193 info = g_new (GtkXIMInfo, 1);
194 open_ims = g_slist_prepend (open_ims, im);
196 info->locale = g_strdup (locale);
207 gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
209 GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
210 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
212 parent_class = g_type_class_peek_parent (class);
214 im_context_class->set_client_window = gtk_im_context_xim_set_client_window;
215 im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
216 im_context_class->reset = gtk_im_context_xim_reset;
217 im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
218 im_context_class->focus_in = gtk_im_context_xim_focus_in;
219 im_context_class->focus_out = gtk_im_context_xim_focus_out;
220 im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
221 im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
222 gobject_class->finalize = gtk_im_context_xim_finalize;
226 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
228 im_context_xim->use_preedit = TRUE;
232 gtk_im_context_xim_finalize (GObject *obj)
234 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (obj);
238 XDestroyIC (context_xim->ic);
239 context_xim->ic = NULL;
242 g_free (context_xim->mb_charset);
246 reinitialize_ic (GtkIMContextXIM *context_xim)
250 XDestroyIC (context_xim->ic);
251 context_xim->ic = NULL;
253 if (context_xim->preedit_length)
255 context_xim->preedit_length = 0;
256 g_signal_emit_by_name (context_xim, "preedit_changed");
262 gtk_im_context_xim_set_client_window (GtkIMContext *context,
263 GdkWindow *client_window)
265 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
267 reinitialize_ic (context_xim);
268 context_xim->client_window = client_window;
272 gtk_im_context_xim_new (void)
275 GtkIMContextXIM *result;
276 const gchar *charset;
278 info = get_im (setlocale (LC_CTYPE, NULL));
282 result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
284 result->im_info = info;
286 g_get_charset (&charset);
287 result->mb_charset = g_strdup (charset);
289 return GTK_IM_CONTEXT (result);
293 mb_to_utf8 (GtkIMContextXIM *context_xim,
296 GError *error = NULL;
299 if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
300 result = g_strdup (str);
303 result = g_convert (str, -1,
304 "UTF-8", context_xim->mb_charset,
308 g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
309 g_error_free (error);
317 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
320 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
321 XIC ic = gtk_im_context_xim_get_ic (context_xim);
322 gchar static_buffer[256];
323 gchar *buffer = static_buffer;
324 gint buffer_size = sizeof(static_buffer) - 1;
328 gboolean result = FALSE;
330 XKeyPressedEvent xevent;
335 xevent.type = KeyPress;
336 xevent.serial = 0; /* hope it doesn't matter */
337 xevent.send_event = event->send_event;
338 xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
339 xevent.window = GDK_DRAWABLE_XID (event->window);
340 xevent.root = GDK_ROOT_WINDOW();
341 xevent.subwindow = xevent.window;
342 xevent.time = event->time;
343 xevent.x = xevent.x_root = 0;
344 xevent.y = xevent.y_root = 0;
345 xevent.state = event->state;
346 xevent.keycode = event->keyval ? XKeysymToKeycode (xevent.display, event->keyval) : 0;
347 xevent.same_screen = True;
349 if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
353 num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
355 if (status == XBufferOverflow)
357 buffer_size = num_bytes;
358 buffer = g_malloc (num_bytes + 1);
362 /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
363 * here ... do input methods actually change the keysym? we can't really
364 * feed it back to accelerator processing at this point...
366 if (status == XLookupChars || status == XLookupBoth)
370 buffer[num_bytes] = '\0';
372 result_utf8 = mb_to_utf8 (context_xim, buffer);
375 if ((guchar)result_utf8[0] >= 0x20) /* Some IM have a nasty habit of converting
376 * control characters into strings
379 g_signal_emit_by_name (context, "commit", result_utf8);
383 g_free (result_utf8);
391 gtk_im_context_xim_focus_in (GtkIMContext *context)
393 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
394 XIC ic = gtk_im_context_xim_get_ic (context_xim);
404 gtk_im_context_xim_focus_out (GtkIMContext *context)
406 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
407 XIC ic = gtk_im_context_xim_get_ic (context_xim);
417 gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
420 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
421 XIC ic = gtk_im_context_xim_get_ic (context_xim);
423 XVaNestedList preedit_attr;
432 preedit_attr = XVaCreateNestedList (0,
433 XNSpotLocation, &spot,
436 XNPreeditAttributes, preedit_attr,
444 gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
445 gboolean use_preedit)
447 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
449 use_preedit = use_preedit != FALSE;
451 if (context_xim->use_preedit != use_preedit)
453 context_xim->use_preedit = use_preedit;
454 reinitialize_ic (context_xim);
461 gtk_im_context_xim_reset (GtkIMContext *context)
463 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
464 XIC ic = gtk_im_context_xim_get_ic (context_xim);
467 /* restore conversion state after resetting ic later */
468 XIMPreeditState preedit_state = XIMPreeditUnKnown;
469 XVaNestedList preedit_attr;
470 gboolean have_preedit_state = FALSE;
476 preedit_attr = XVaCreateNestedList(0,
477 XNPreeditState, &preedit_state,
479 if (!XGetICValues(ic,
480 XNPreeditAttributes, preedit_attr,
482 have_preedit_state = TRUE;
486 result = XmbResetIC (ic);
488 preedit_attr = XVaCreateNestedList(0,
489 XNPreeditState, preedit_state,
491 if (have_preedit_state)
493 XNPreeditAttributes, preedit_attr,
500 char *result_utf8 = mb_to_utf8 (context_xim, result);
503 g_signal_emit_by_name (context, "commit", result_utf8);
504 g_free (result_utf8);
508 if (context_xim->preedit_length)
510 context_xim->preedit_length = 0;
511 g_signal_emit_by_name (context, "preedit_changed");
517 /* Mask of feedback bits that we render
519 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
522 add_feedback_attr (PangoAttrList *attrs,
524 XIMFeedback feedback,
528 PangoAttribute *attr;
530 gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
531 gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
533 if (feedback & XIMUnderline)
535 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
536 attr->start_index = start_index;
537 attr->end_index = end_index;
539 pango_attr_list_change (attrs, attr);
542 if (feedback & XIMReverse)
544 attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
545 attr->start_index = start_index;
546 attr->end_index = end_index;
548 pango_attr_list_change (attrs, attr);
550 attr = pango_attr_background_new (0, 0, 0);
551 attr->start_index = start_index;
552 attr->end_index = end_index;
554 pango_attr_list_change (attrs, attr);
557 if (feedback & ~FEEDBACK_MASK)
558 g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
562 gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
564 PangoAttrList **attrs,
567 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
568 gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
573 XIMFeedback last_feedback = 0;
576 *attrs = pango_attr_list_new ();
578 for (i = 0; i < context_xim->preedit_length; i++)
580 XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
581 if (new_feedback != last_feedback)
584 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
586 last_feedback = new_feedback;
592 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
601 *cursor_pos = context_xim->preedit_cursor;
605 preedit_start_callback (XIC xic,
606 XPointer client_data,
609 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
611 g_signal_emit_by_name (context, "preedit_start");
612 g_print ("Starting preedit!\n");
616 preedit_done_callback (XIC xic,
617 XPointer client_data,
620 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
622 g_signal_emit_by_name (context, "preedit_end");
623 g_print ("Ending preedit!\n");
627 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
629 gint text_length = 0;
630 GError *error = NULL;
631 gchar *result = NULL;
633 if (xim_text && xim_text->string.multi_byte)
635 if (xim_text->encoding_is_wchar)
637 g_warning ("Wide character return from Xlib not currently supported");
642 if (strcmp (context->mb_charset, "UTF-8") == 0)
643 result = g_strdup (xim_text->string.multi_byte);
645 result = g_convert (xim_text->string.multi_byte,
653 text_length = g_utf8_strlen (result, -1);
655 if (text_length != xim_text->length)
657 g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
662 g_warning ("Error converting text from IM to UCS-4: %s", error->message);
663 g_error_free (error);
680 preedit_draw_callback (XIC xic,
681 XPointer client_data,
682 XIMPreeditDrawCallbackStruct *call_data)
684 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
686 XIMText *new_xim_text = call_data->text;
687 gint new_text_length;
688 gunichar *new_text = NULL;
694 gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
695 gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
697 context->preedit_cursor = call_data->caret;
699 if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
700 g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
701 call_data->chg_first, call_data->chg_length, context->preedit_length);
703 new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
706 new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
710 diff = new_text_length - chg_length;
711 new_length = context->preedit_length + diff;
713 if (new_length > context->preedit_size)
715 context->preedit_size = new_length;
716 context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
717 context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
722 for (i = chg_first + chg_length ; i < context->preedit_length; i++)
724 context->preedit_chars[i + diff] = context->preedit_chars[i];
725 context->feedbacks[i + diff] = context->feedbacks[i];
730 for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
732 context->preedit_chars[i + diff] = context->preedit_chars[i];
733 context->feedbacks[i + diff] = context->feedbacks[i];
737 for (i = 0; i < new_text_length; i++)
739 context->preedit_chars[chg_first + i] = new_text[i];
740 context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
743 context->preedit_length += diff;
748 g_signal_emit_by_name (context, "preedit_changed");
753 preedit_caret_callback (XIC xic,
754 XPointer client_data,
755 XIMPreeditCaretCallbackStruct *call_data)
757 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
759 if (call_data->direction == XIMAbsolutePosition)
761 context->preedit_cursor = call_data->position;
762 g_signal_emit_by_name (context, "preedit_changed");
766 g_warning ("Caret movement command: %d %d %d not supported",
767 call_data->position, call_data->direction, call_data->style);
772 status_start_callback (XIC xic,
773 XPointer client_data,
776 g_print ("Status start\n");
780 status_done_callback (XIC xic,
781 XPointer client_data,
784 g_print ("Status done!\n");
788 status_draw_callback (XIC xic,
789 XPointer client_data,
790 XIMStatusDrawCallbackStruct *call_data)
792 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
794 g_print ("Status draw\n");
795 if (call_data->type == XIMTextType)
798 xim_text_to_utf8 (context, call_data->data.text, &text);
801 g_print (" %s\n", text);
805 g_print (" bitmap id = %#lx\n", call_data->data.bitmap);
810 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
812 const char *name1 = NULL;
813 XVaNestedList list1 = NULL;
814 const char *name2 = NULL;
815 XVaNestedList list2 = NULL;
817 if (!context_xim->ic && context_xim->client_window)
819 if (!context_xim->use_preedit)
821 context_xim->ic = XCreateIC (context_xim->im_info->im,
822 XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
823 XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
825 return context_xim->ic;
828 if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
830 context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
831 context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
832 context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
833 context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
834 context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
835 context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
836 context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
837 context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
839 name1 = XNPreeditAttributes;
840 list1 = XVaCreateNestedList (0,
841 XNPreeditStartCallback, &context_xim->preedit_start_callback,
842 XNPreeditDoneCallback, &context_xim->preedit_done_callback,
843 XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
844 XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
848 if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
850 XVaNestedList status_attrs;
852 context_xim->status_start_callback.client_data = (XPointer)context_xim;
853 context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
854 context_xim->status_done_callback.client_data = (XPointer)context_xim;
855 context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
856 context_xim->status_draw_callback.client_data = (XPointer)context_xim;
857 context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
859 status_attrs = XVaCreateNestedList (0,
860 XNStatusStartCallback, &context_xim->status_start_callback,
861 XNStatusDoneCallback, &context_xim->status_done_callback,
862 XNStatusDrawCallback, &context_xim->status_draw_callback,
867 name1 = XNStatusAttributes;
868 list1 = status_attrs;
872 name2 = XNStatusAttributes;
873 list2 = status_attrs;
877 context_xim->ic = XCreateIC (context_xim->im_info->im,
878 XNInputStyle, context_xim->im_info->style,
879 XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
890 return context_xim->ic;