]> Pileus Git - ~andy/gtk/blobdiff - modules/input/gtkimcontextxim.c
Fix incorrect return value, filter out returns of 0x7f for the delete key.
[~andy/gtk] / modules / input / gtkimcontextxim.c
index bf0fde1dbbde87b48d03e9133f02e07c39d1d183..a76b667de1de86dadd52c1d9b5c49cc4f1b3c7dd 100644 (file)
@@ -20,7 +20,9 @@
 #include "locale.h"
 #include <string.h>
 
+#include "gtk/gtklabel.h"
 #include "gtk/gtksignal.h"
+#include "gtk/gtkwindow.h"
 #include "gtkimcontextxim.h"
 
 struct _GtkXIMInfo
@@ -38,11 +40,22 @@ static void     gtk_im_context_xim_set_client_window  (GtkIMContext          *co
 static gboolean gtk_im_context_xim_filter_keypress    (GtkIMContext          *context,
                                                       GdkEventKey           *key);
 static void     gtk_im_context_xim_reset              (GtkIMContext          *context);
+static void     gtk_im_context_xim_focus_in           (GtkIMContext          *context);
+static void     gtk_im_context_xim_focus_out          (GtkIMContext          *context);
+static void     gtk_im_context_xim_set_cursor_location (GtkIMContext          *context,
+                                                      GdkRectangle             *area);
+static void     gtk_im_context_xim_set_use_preedit    (GtkIMContext          *context,
+                                                      gboolean               use_preedit);
 static void     gtk_im_context_xim_get_preedit_string (GtkIMContext          *context,
                                                       gchar                **str,
                                                       PangoAttrList        **attrs,
                                                       gint                  *cursor_pos);
 
+static void status_window_show     (GtkIMContextXIM *context_xim);
+static void status_window_hide     (GtkIMContextXIM *context_xim);
+static void status_window_set_text (GtkIMContextXIM *context_xim,
+                                   const gchar     *text);
+
 static XIC       gtk_im_context_xim_get_ic            (GtkIMContextXIM *context_xim);
 static GObjectClass *parent_class;
 
@@ -209,12 +222,17 @@ gtk_im_context_xim_class_init (GtkIMContextXIMClass *class)
   im_context_class->filter_keypress = gtk_im_context_xim_filter_keypress;
   im_context_class->reset = gtk_im_context_xim_reset;
   im_context_class->get_preedit_string = gtk_im_context_xim_get_preedit_string;
+  im_context_class->focus_in = gtk_im_context_xim_focus_in;
+  im_context_class->focus_out = gtk_im_context_xim_focus_out;
+  im_context_class->set_cursor_location = gtk_im_context_xim_set_cursor_location;
+  im_context_class->set_use_preedit = gtk_im_context_xim_set_use_preedit;
   gobject_class->finalize = gtk_im_context_xim_finalize;
 }
 
 static void
 gtk_im_context_xim_init (GtkIMContextXIM *im_context_xim)
 {
+  im_context_xim->use_preedit = TRUE;
 }
 
 static void
@@ -232,17 +250,28 @@ gtk_im_context_xim_finalize (GObject *obj)
 }
 
 static void
-gtk_im_context_xim_set_client_window (GtkIMContext          *context,
-                                     GdkWindow             *client_window)
+reinitialize_ic (GtkIMContextXIM *context_xim)
 {
-  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
-
   if (context_xim->ic)
     {
       XDestroyIC (context_xim->ic);
       context_xim->ic = NULL;
+
+      if (context_xim->preedit_length)
+       {
+         context_xim->preedit_length = 0;
+         g_signal_emit_by_name (context_xim, "preedit_changed");
+       }
     }
+}
+
+static void
+gtk_im_context_xim_set_client_window (GtkIMContext          *context,
+                                     GdkWindow             *client_window)
+{
+  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
 
+  reinitialize_ic (context_xim);
   context_xim->client_window = client_window;
 }
 
@@ -251,13 +280,13 @@ gtk_im_context_xim_new (void)
 {
   GtkXIMInfo *info;
   GtkIMContextXIM *result;
-  gchar *charset;
+  const gchar *charset;
 
   info = get_im (setlocale (LC_CTYPE, NULL));
   if (!info)
     return NULL;
 
-  result = GTK_IM_CONTEXT_XIM (gtk_type_new (GTK_TYPE_IM_CONTEXT_XIM));
+  result = GTK_IM_CONTEXT_XIM (g_object_new (GTK_TYPE_IM_CONTEXT_XIM, NULL));
 
   result->im_info = info;
   
@@ -274,14 +303,18 @@ mb_to_utf8 (GtkIMContextXIM *context_xim,
   GError *error = NULL;
   gchar *result;
 
-  result = g_convert (str, -1,
-                     "UTF-8", context_xim->mb_charset,
-                     NULL, NULL, &error);
-
-  if (!result)
+  if (strcmp (context_xim->mb_charset, "UTF-8") == 0)
+    result = g_strdup (str);
+  else
     {
-      g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
-      g_error_free (error);
+      result = g_convert (str, -1,
+                         "UTF-8", context_xim->mb_charset,
+                         NULL, NULL, &error);
+      if (!result)
+       {
+         g_warning ("Error converting text from IM to UTF-8: %s\n", error->message);
+         g_error_free (error);
+       }
     }
   
   return result;
@@ -306,7 +339,7 @@ gtk_im_context_xim_filter_keypress (GtkIMContext *context,
   if (!ic)
     return FALSE;
 
-  xevent.type = KeyPress;
+  xevent.type = (event->type == GDK_KEY_PRESS) ? KeyPress : KeyRelease;
   xevent.serial = 0;           /* hope it doesn't matter */
   xevent.send_event = event->send_event;
   xevent.display = GDK_DRAWABLE_XDISPLAY (event->window);
@@ -346,11 +379,12 @@ gtk_im_context_xim_filter_keypress (GtkIMContext *context,
       result_utf8 = mb_to_utf8 (context_xim, buffer);
       if (result_utf8)
        {
-         if ((guchar)result_utf8[0] >= 0x20) /* Some IM have a nasty habit of converting
-                                              * control characters into strings
-                                              */
+         if ((guchar)result_utf8[0] >= 0x20 &&
+             result_utf8[0] != 0x7f) /* Some IM have a nasty habit of converting
+                                      * control characters into strings
+                                      */
            {
-             gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
+             g_signal_emit_by_name (context, "commit", result_utf8);
              result = TRUE;
            }
          
@@ -358,7 +392,83 @@ gtk_im_context_xim_filter_keypress (GtkIMContext *context,
        }
     }
 
-  return FALSE;
+  return result;
+}
+
+static void
+gtk_im_context_xim_focus_in (GtkIMContext *context)
+{
+  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
+  XIC ic = gtk_im_context_xim_get_ic (context_xim);
+
+  if (!ic)
+    return;
+
+  XSetICFocus (ic);
+
+  status_window_show (context_xim);
+
+  return;
+}
+
+static void
+gtk_im_context_xim_focus_out (GtkIMContext *context)
+{
+  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
+  XIC ic = gtk_im_context_xim_get_ic (context_xim);
+
+  if (!ic)
+    return;
+
+  XUnsetICFocus (ic);
+
+  status_window_hide (context_xim);
+
+  return;
+}
+
+static void
+gtk_im_context_xim_set_cursor_location (GtkIMContext *context,
+                                       GdkRectangle *area)
+{
+  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
+  XIC ic = gtk_im_context_xim_get_ic (context_xim);
+
+  XVaNestedList preedit_attr;
+  XPoint          spot;
+
+  if (!ic)
+    return;
+
+  spot.x = area->x;
+  spot.y = area->y;
+
+  preedit_attr = XVaCreateNestedList (0,
+                                     XNSpotLocation, &spot,
+                                     0);
+  XSetICValues (ic,
+               XNPreeditAttributes, preedit_attr,
+               NULL);
+  XFree(preedit_attr);
+
+  return;
+}
+
+static void
+gtk_im_context_xim_set_use_preedit (GtkIMContext *context,
+                                   gboolean      use_preedit)
+{
+  GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
+
+  use_preedit = use_preedit != FALSE;
+
+  if (context_xim->use_preedit != use_preedit)
+    {
+      context_xim->use_preedit = use_preedit;
+      reinitialize_ic (context_xim);
+    }
+
+  return;
 }
 
 static void
@@ -377,6 +487,9 @@ gtk_im_context_xim_reset (GtkIMContext *context)
     return;
   
 
+  if (context_xim->preedit_length == 0)
+    return;
+
   preedit_attr = XVaCreateNestedList(0,
                                      XNPreeditState, &preedit_state,
                                      0);
@@ -404,7 +517,7 @@ gtk_im_context_xim_reset (GtkIMContext *context)
       char *result_utf8 = mb_to_utf8 (context_xim, result);
       if (result_utf8)
        {
-         gtk_signal_emit_by_name (GTK_OBJECT (context), "commit", result_utf8);
+         g_signal_emit_by_name (context, "commit", result_utf8);
          g_free (result_utf8);
        }
     }
@@ -412,7 +525,7 @@ gtk_im_context_xim_reset (GtkIMContext *context)
   if (context_xim->preedit_length)
     {
       context_xim->preedit_length = 0;
-      gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
+      g_signal_emit_by_name (context, "preedit_changed");
     }
 
   XFree (result);
@@ -469,7 +582,7 @@ gtk_im_context_xim_get_preedit_string (GtkIMContext   *context,
                                       gint           *cursor_pos)
 {
   GtkIMContextXIM *context_xim = GTK_IM_CONTEXT_XIM (context);
-  gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length);
+  gchar *utf8 = g_ucs4_to_utf8 (context_xim->preedit_chars, context_xim->preedit_length, NULL, NULL, NULL);
 
   if (attrs)
     {
@@ -512,8 +625,7 @@ preedit_start_callback (XIC      xic,
 {
   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
   
-  gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_start");
-  g_print ("Starting preedit!\n");
+  g_signal_emit_by_name (context, "preedit_start");
 }                   
 
 static void
@@ -523,8 +635,7 @@ preedit_done_callback (XIC      xic,
 {
   GtkIMContext *context = GTK_IM_CONTEXT (client_data);
   
-  gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_end");  
-  g_print ("Ending preedit!\n");
+  g_signal_emit_by_name (context, "preedit_end");  
 }                   
 
 static gint
@@ -543,11 +654,14 @@ xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
          return 0;
        }
 
-      result = g_convert (xim_text->string.multi_byte,
-                         -1,
-                         "UTF-8",
-                         context->mb_charset,
-                         NULL, &text_length,  &error);
+      if (strcmp (context->mb_charset, "UTF-8") == 0)
+       result = g_strdup (xim_text->string.multi_byte);
+      else
+       result = g_convert (xim_text->string.multi_byte,
+                           -1,
+                           "UTF-8",
+                           context->mb_charset,
+                           NULL, NULL, &error);
       
       if (result)
        {
@@ -562,6 +676,9 @@ xim_text_to_utf8 (GtkIMContextXIM *context, XIMText *xim_text, gchar **text)
        {
          g_warning ("Error converting text from IM to UCS-4: %s", error->message);
          g_error_free (error);
+
+         *text = NULL;
+         return 0;
        }
 
       *text = result;
@@ -601,7 +718,7 @@ preedit_draw_callback (XIC                           xic,
   new_text_length = xim_text_to_utf8 (context, new_xim_text, &tmp);
   if (tmp)
     {
-      new_text = g_utf8_to_ucs4 (tmp, -1);
+      new_text = g_utf8_to_ucs4_fast (tmp, -1, NULL);
       g_free (tmp);
     }
   
@@ -643,7 +760,7 @@ preedit_draw_callback (XIC                           xic,
   if (new_text)
     g_free (new_text);
 
-  gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
+  g_signal_emit_by_name (context, "preedit_changed");
 }
     
 
@@ -657,7 +774,7 @@ preedit_caret_callback (XIC                            xic,
   if (call_data->direction == XIMAbsolutePosition)
     {
       context->preedit_cursor = call_data->position;
-      gtk_signal_emit_by_name (GTK_OBJECT (context), "preedit_changed");
+      g_signal_emit_by_name (context, "preedit_changed");
     }
   else
     {
@@ -671,7 +788,7 @@ status_start_callback (XIC      xic,
                       XPointer client_data,
                       XPointer call_data)
 {
-  g_print ("Status start\n");
+  return;
 } 
 
 static void
@@ -679,7 +796,7 @@ status_done_callback (XIC      xic,
                      XPointer client_data,
                      XPointer call_data)
 {
-  g_print ("Status done!\n");
+  return;
 }
 
 static void
@@ -689,18 +806,19 @@ status_draw_callback (XIC      xic,
 {
   GtkIMContextXIM *context = GTK_IM_CONTEXT_XIM (client_data);
 
-  g_print ("Status draw\n");
   if (call_data->type == XIMTextType)
     {
       gchar *text;
       xim_text_to_utf8 (context, call_data->data.text, &text);
 
       if (text)
-       g_print ("  %s\n", text);
+       status_window_set_text (context, text);
+      else
+       status_window_set_text (context, "");
     }
   else                         /* bitmap */
     {
-      g_print ("   bitmap id = %#lx\n", call_data->data.bitmap);
+      g_print ("Status drawn with bitmap - id = %#lx\n", call_data->data.bitmap);
     }
 }
 
@@ -714,6 +832,15 @@ gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
 
   if (!context_xim->ic && context_xim->client_window)
     {
+      if (!context_xim->use_preedit)
+       {
+         context_xim->ic = XCreateIC (context_xim->im_info->im,
+                                      XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
+                                      XNClientWindow, GDK_DRAWABLE_XID (context_xim->client_window),
+                                      NULL);
+         return context_xim->ic;
+       }
+
       if ((context_xim->im_info->style & PREEDIT_MASK) == XIMPreeditCallbacks)
        {
          context_xim->preedit_start_callback.client_data = (XPointer)context_xim;
@@ -778,3 +905,175 @@ gtk_im_context_xim_get_ic (GtkIMContextXIM *context_xim)
 
   return context_xim->ic;
 }
+
+/**************************
+ *                        *
+ * Status Window handling *
+ *                        *
+ **************************/
+
+static gboolean
+status_window_expose_event (GtkWidget      *widget,
+                           GdkEventExpose *event)
+{
+  gdk_draw_rectangle (widget->window,
+                     widget->style->base_gc [GTK_STATE_NORMAL],
+                     TRUE,
+                     0, 0,
+                     widget->allocation.width, widget->allocation.height);
+  gdk_draw_rectangle (widget->window,
+                     widget->style->text_gc [GTK_STATE_NORMAL],
+                     FALSE,
+                     0, 0,
+                     widget->allocation.width - 1, widget->allocation.height - 1);
+
+  return FALSE;
+}
+
+static void
+status_window_style_set (GtkWidget *toplevel,
+                        GtkStyle  *previous_style,
+                        GtkWidget *label)
+{
+  gint i;
+  
+  for (i = 0; i < 5; i++)
+    gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
+}
+
+static void
+status_window_destroy (GtkWidget *toplevel,
+                      GtkWidget *status_window)
+{
+  gtk_widget_destroy (status_window);
+  g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", NULL);
+}
+
+static gboolean
+status_window_configure (GtkWidget         *toplevel,
+                        GdkEventConfigure *event,
+                        GtkWidget         *status_window)
+{
+  GdkRectangle rect;
+  GtkRequisition requisition;
+  gint y;
+
+  gdk_window_get_frame_extents (toplevel->window, &rect);
+  gtk_widget_size_request (status_window, &requisition);
+
+  if (rect.y + rect.height + requisition.height < gdk_screen_height ())
+    y = rect.y + rect.height;
+  else
+    y = gdk_screen_height () - requisition.height;
+  
+  gtk_window_move (GTK_WINDOW (status_window), rect.x, y);
+
+  return FALSE;
+}
+
+static GtkWidget *
+status_window_get (GtkIMContextXIM *context_xim,
+                  gboolean         create)
+{
+  GdkWindow *toplevel_gdk;
+  GtkWidget *toplevel;
+  GtkWidget *status_window;
+  GtkWidget *status_label;
+  
+  if (!context_xim->client_window)
+    return NULL;
+
+  toplevel_gdk = context_xim->client_window;
+  while (TRUE)
+    {
+      GdkWindow *parent = gdk_window_get_parent (toplevel_gdk);
+      if (parent == gdk_get_default_root_window ())
+       break;
+      else
+       toplevel_gdk = parent;
+    }
+
+  gdk_window_get_user_data (toplevel_gdk, (gpointer *)&toplevel);
+  if (!toplevel)
+    return NULL;
+
+  status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
+  if (status_window || !create)
+    return status_window;
+
+  status_window = gtk_window_new (GTK_WINDOW_POPUP);
+
+  gtk_window_set_policy (GTK_WINDOW (status_window), FALSE, FALSE, FALSE);
+  gtk_widget_set_app_paintable (status_window, TRUE);
+
+  status_label = gtk_label_new ("");
+  gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
+  gtk_widget_show (status_label);
+  
+  gtk_container_add (GTK_CONTAINER (status_window), status_label);
+
+  g_signal_connect (toplevel, "destroy",
+                   G_CALLBACK (status_window_destroy), status_window);
+  g_signal_connect (toplevel, "configure_event",
+                   G_CALLBACK (status_window_configure), status_window);
+
+  status_window_configure (toplevel, NULL, status_window);
+  
+  g_signal_connect (status_window, "style_set",
+                   G_CALLBACK (status_window_style_set), status_label);
+  g_signal_connect (status_window, "expose_event",
+                   G_CALLBACK (status_window_expose_event), NULL);
+  
+  g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
+
+  return status_window;
+}
+
+static gboolean
+status_window_has_text (GtkWidget *status_window)
+{
+  GtkWidget *label = GTK_BIN (status_window)->child;
+  const gchar *text = gtk_label_get_text (GTK_LABEL (label));
+
+  return text[0] != '\0';
+}
+
+static void
+status_window_show (GtkIMContextXIM *context_xim)
+{
+  GtkWidget *status_window = status_window_get (context_xim, TRUE);
+
+  context_xim->status_visible = TRUE;
+  
+  if (status_window && status_window_has_text (status_window))
+    gtk_widget_show (status_window);
+}
+
+static void
+status_window_hide (GtkIMContextXIM *context_xim)
+{
+  GtkWidget *status_window = status_window_get (context_xim, FALSE);
+
+  context_xim->status_visible = FALSE;
+  
+  if (status_window)
+    gtk_widget_hide (status_window);
+}
+
+static void
+status_window_set_text (GtkIMContextXIM *context_xim,
+                       const gchar     *text)
+{
+  GtkWidget *status_window = status_window_get (context_xim, TRUE);
+
+  if (status_window)
+    {
+      GtkWidget *label = GTK_BIN (status_window)->child;
+      gtk_label_set_text (GTK_LABEL (label), text);
+      
+      if (context_xim->status_visible && status_window_has_text (status_window))
+       gtk_widget_show (status_window);
+      else
+       gtk_widget_hide (status_window);
+    }
+}