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_pos (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_pos = gtk_im_context_xim_set_cursor_pos;
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;
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 result = g_convert (str, -1,
285 "UTF-8", context_xim->mb_charset,
290 g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
291 g_error_free (error);
298 gtk_im_context_xim_filter_keypress (GtkIMContext *context,
301 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
302 XIC ic = gtk_im_context_xim_get_ic (context_xim);
303 gchar static_buffer[256];
304 gchar *buffer = static_buffer;
305 gint buffer_size = sizeof(static_buffer) - 1;
309 gboolean result = FALSE;
311 XKeyPressedEvent xevent;
316 xevent.type = KeyPress;
317 xevent.serial = 0; /* hope it doesn't matter */
318 xevent.send_event = event->send_event;
319 xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
320 xevent.window = GDK_DRAWABLE_XID (event->window);
321 xevent.root = GDK_ROOT_WINDOW();
322 xevent.subwindow = xevent.window;
323 xevent.time = event->time;
324 xevent.x = xevent.x_root = 0;
325 xevent.y = xevent.y_root = 0;
326 xevent.state = event->state;
327 xevent.keycode = event->keyval ? XKeysymToKeycode (xevent.display, event->keyval) : 0;
328 xevent.same_screen = True;
330 if (XFilterEvent ((XEvent *)&xevent, GDK_DRAWABLE_XID (context_xim->client_window)))
334 num_bytes = XmbLookupString (ic, &xevent, buffer, buffer_size, &keysym, &status);
336 if (status == XBufferOverflow)
338 buffer_size = num_bytes;
339 buffer = g_malloc (num_bytes + 1);
343 /* I don't know how we should properly handle XLookupKeysym or XLookupBoth
344 * here ... do input methods actually change the keysym? we can't really
345 * feed it back to accelerator processing at this point...
347 if (status == XLookupChars || status == XLookupBoth)
351 buffer[num_bytes] = '\0';
353 result_utf8 = mb_to_utf8 (context_xim, buffer);
356 if ((guchar)result_utf8[0] >= 0x20) /* Some IM have a nasty habit of converting
357 * control characters into strings
360 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
364 g_free (result_utf8);
372 gtk_im_context_xim_focus_in (GtkIMContext *context)
374 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
375 XIC ic = gtk_im_context_xim_get_ic (context_xim);
385 gtk_im_context_xim_focus_out (GtkIMContext *context)
387 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
388 XIC ic = gtk_im_context_xim_get_ic (context_xim);
398 gtk_im_context_xim_set_cursor_pos (GtkIMContext *context,
401 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
402 XIC ic = gtk_im_context_xim_get_ic (context_xim);
404 XVaNestedList preedit_attr;
413 preedit_attr = XVaCreateNestedList (0,
414 XNSpotLocation, &spot,
417 XNPreeditAttributes, preedit_attr,
425 gtk_im_context_xim_reset (GtkIMContext *context)
427 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
428 XIC ic = gtk_im_context_xim_get_ic (context_xim);
431 /* restore conversion state after resetting ic later */
432 XIMPreeditState preedit_state = XIMPreeditUnKnown;
433 XVaNestedList preedit_attr;
434 gboolean have_preedit_state = FALSE;
440 preedit_attr = XVaCreateNestedList(0,
441 XNPreeditState, &preedit_state,
443 if (!XGetICValues(ic,
444 XNPreeditAttributes, preedit_attr,
446 have_preedit_state = TRUE;
450 result = XmbResetIC (ic);
452 preedit_attr = XVaCreateNestedList(0,
453 XNPreeditState, preedit_state,
455 if (have_preedit_state)
457 XNPreeditAttributes, preedit_attr,
464 char *result_utf8 = mb_to_utf8 (context_xim, result);
467 gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
468 g_free (result_utf8);
472 if (context_xim->preedit_length)
474 context_xim->preedit_length = 0;
475 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
481 /* Mask of feedback bits that we render
483 #define FEEDBACK_MASK (XIMReverse | XIMUnderline)
486 add_feedback_attr (PangoAttrList *attrs,
488 XIMFeedback feedback,
492 PangoAttribute *attr;
494 gint start_index = g_utf8_offset_to_pointer (str, start_pos) - str;
495 gint end_index = g_utf8_offset_to_pointer (str, end_pos) - str;
497 if (feedback & XIMUnderline)
499 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
500 attr->start_index = start_index;
501 attr->end_index = end_index;
503 pango_attr_list_change (attrs, attr);
506 if (feedback & XIMReverse)
508 attr = pango_attr_foreground_new (0xffff, 0xffff, 0xffff);
509 attr->start_index = start_index;
510 attr->end_index = end_index;
512 pango_attr_list_change (attrs, attr);
514 attr = pango_attr_background_new (0, 0, 0);
515 attr->start_index = start_index;
516 attr->end_index = end_index;
518 pango_attr_list_change (attrs, attr);
521 if (feedback & ~FEEDBACK_MASK)
522 g_warning ("Unrendered feedback style: %#lx", feedback & ~FEEDBACK_MASK);
526 gtk_im_context_xim_get_preedit_string (GtkIMContext *context,
528 PangoAttrList **attrs,
531 GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
532 gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
537 XIMFeedback last_feedback = 0;
540 *attrs = pango_attr_list_new ();
542 for (i = 0; i < context_xim->preedit_length; i++)
544 XIMFeedback new_feedback = context_xim->feedbacks[i] & FEEDBACK_MASK;
545 if (new_feedback != last_feedback)
548 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
550 last_feedback = new_feedback;
556 add_feedback_attr (*attrs, utf8, last_feedback, start, i);
565 *cursor_pos = context_xim->preedit_cursor;
569 preedit_start_callback (XIC xic,
570 XPointer client_data,
573 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
575 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_start");
576 g_print ("Starting preedit!\n");
580 preedit_done_callback (XIC xic,
581 XPointer client_data,
584 GtkIMContext *context = GTK_IM_CONTEXT (client_data);
586 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_end");
587 g_print ("Ending preedit!\n");
591 xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
593 gint text_length = 0;
594 GError *error = NULL;
595 gchar *result = NULL;
597 if (xim_text && xim_text->string.multi_byte)
599 if (xim_text->encoding_is_wchar)
601 g_warning ("Wide character return from Xlib not currently supported");
606 result = g_convert (xim_text->string.multi_byte,
610 NULL, &text_length, &error);
614 text_length = g_utf8_strlen (result, -1);
616 if (text_length != xim_text->length)
618 g_warning ("Size mismatch when converting text from input method: supplied length = %d\n, result length = %d", xim_text->length, text_length);
623 g_warning ("Error converting text from IM to UCS-4: %s", error->message);
624 g_error_free (error);
638 preedit_draw_callback (XIC xic,
639 XPointer client_data,
640 XIMPreeditDrawCallbackStruct *call_data)
642 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
644 XIMText *new_xim_text = call_data->text;
645 gint new_text_length;
646 gunichar *new_text = NULL;
652 gint chg_first = CLAMP (call_data->chg_first, 0, context->preedit_length);
653 gint chg_length = CLAMP (call_data->chg_length, 0, context->preedit_length - chg_first);
655 context->preedit_cursor = call_data->caret;
657 if (chg_first != call_data->chg_first || chg_length != call_data->chg_length)
658 g_warning ("Invalid change to preedit string, first=%d length=%d (orig length == %d)",
659 call_data->chg_first, call_data->chg_length, context->preedit_length);
661 new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
664 new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
668 diff = new_text_length - chg_length;
669 new_length = context->preedit_length + diff;
671 if (new_length > context->preedit_size)
673 context->preedit_size = new_length;
674 context->preedit_chars = g_renew (gunichar, context->preedit_chars, new_length);
675 context->feedbacks = g_renew (XIMFeedback, context->feedbacks, new_length);
680 for (i = chg_first + chg_length ; i < context->preedit_length; i++)
682 context->preedit_chars[i + diff] = context->preedit_chars[i];
683 context->feedbacks[i + diff] = context->feedbacks[i];
688 for (i = context->preedit_length - 1; i >= chg_first + chg_length ; i--)
690 context->preedit_chars[i + diff] = context->preedit_chars[i];
691 context->feedbacks[i + diff] = context->feedbacks[i];
695 for (i = 0; i < new_text_length; i++)
697 context->preedit_chars[chg_first + i] = new_text[i];
698 context->feedbacks[chg_first + i] = new_xim_text->feedback[i];
701 context->preedit_length += diff;
706 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
711 preedit_caret_callback (XIC xic,
712 XPointer client_data,
713 XIMPreeditCaretCallbackStruct *call_data)
715 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
717 if (call_data->direction == XIMAbsolutePosition)
719 context->preedit_cursor = call_data->position;
720 gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
724 g_warning ("Caret movement command: %d %d %d not supported",
725 call_data->position, call_data->direction, call_data->style);
730 status_start_callback (XIC xic,
731 XPointer client_data,
734 g_print ("Status start\n");
738 status_done_callback (XIC xic,
739 XPointer client_data,
742 g_print ("Status done!\n");
746 status_draw_callback (XIC xic,
747 XPointer client_data,
748 XIMStatusDrawCallbackStruct *call_data)
750 GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
752 g_print ("Status draw\n");
753 if (call_data->type == XIMTextType)
756 xim_text_to_utf8 (context, call_data->data.text, &text);
759 g_print (" %s\n", text);
763 g_print (" bitmap id = %#lx\n", call_data->data.bitmap);
768 gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
770 const char *name1 = NULL;
771 XVaNestedList list1 = NULL;
772 const char *name2 = NULL;
773 XVaNestedList list2 = NULL;
775 if (!context_xim->ic && context_xim->client_window)
777 if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
779 context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
780 context_xim->preedit_start_callback.callback = (XIMProc)preedit_start_callback;
781 context_xim->preedit_done_callback.client_data = (XPointer)context_xim;
782 context_xim->preedit_done_callback.callback = (XIMProc)preedit_done_callback;
783 context_xim->preedit_draw_callback.client_data = (XPointer)context_xim;
784 context_xim->preedit_draw_callback.callback = (XIMProc)preedit_draw_callback;
785 context_xim->preedit_caret_callback.client_data = (XPointer)context_xim;
786 context_xim->preedit_caret_callback.callback = (XIMProc)preedit_caret_callback;
788 name1 = XNPreeditAttributes;
789 list1 = XVaCreateNestedList (0,
790 XNPreeditStartCallback, &context_xim->preedit_start_callback,
791 XNPreeditDoneCallback, &context_xim->preedit_done_callback,
792 XNPreeditDrawCallback, &context_xim->preedit_draw_callback,
793 XNPreeditCaretCallback, &context_xim->preedit_caret_callback,
797 if ((context_xim->im_info->style & STATUS_MASK) == XIMStatusCallbacks)
799 XVaNestedList status_attrs;
801 context_xim->status_start_callback.client_data = (XPointer)context_xim;
802 context_xim->status_start_callback.callback = (XIMProc)status_start_callback;
803 context_xim->status_done_callback.client_data = (XPointer)context_xim;
804 context_xim->status_done_callback.callback = (XIMProc)status_done_callback;
805 context_xim->status_draw_callback.client_data = (XPointer)context_xim;
806 context_xim->status_draw_callback.callback = (XIMProc)status_draw_callback;
808 status_attrs = XVaCreateNestedList (0,
809 XNStatusStartCallback, &context_xim->status_start_callback,
810 XNStatusDoneCallback, &context_xim->status_done_callback,
811 XNStatusDrawCallback, &context_xim->status_draw_callback,
816 name1 = XNStatusAttributes;
817 list1 = status_attrs;
821 name2 = XNStatusAttributes;
822 list2 = status_attrs;
826 context_xim->ic = XCreateIC (context_xim->im_info->im,
827 XNInputStyle, context_xim->im_info->style,
828 XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
839 return context_xim->ic;