[gimp] app: implement IM preedit using an overlay widget



commit 4704c187392e6597afa0d00069d3f2f8071d08dd
Author: Michael Natterer <mitch gimp org>
Date:   Sat Feb 20 15:13:14 2010 +0100

    app: implement IM preedit using an overlay widget
    
    This has several advantages:
    
    - it's always readable, no matter how sick font/colors are.
    - it does not mess up the buffer, which is a model that should not
      contain temporary edit states.
    - preediting does not clutter undo.
    - it fixes the remaining bugs in the old preediting code because that
      code is completely gone now.

 app/tools/gimptexttool-editor.c |  144 +++++++++++++++++++++-------
 app/tools/gimptexttool-editor.h |   27 +++---
 app/tools/gimptexttool.c        |  200 +++++++--------------------------------
 app/tools/gimptexttool.h        |    3 +
 4 files changed, 163 insertions(+), 211 deletions(-)
---
diff --git a/app/tools/gimptexttool-editor.c b/app/tools/gimptexttool-editor.c
index eb93154..c29d7e0 100644
--- a/app/tools/gimptexttool-editor.c
+++ b/app/tools/gimptexttool-editor.c
@@ -74,7 +74,11 @@ static void   gimp_text_tool_enter_text         (GimpTextTool    *text_tool,
 static void   gimp_text_tool_commit_cb          (GtkIMContext    *context,
                                                  const gchar     *str,
                                                  GimpTextTool    *text_tool);
-static void   gimp_text_tool_preedit_changed_cb (GtkIMContext    *context,
+static void   gimp_text_tool_preedit_start      (GtkIMContext    *context,
+                                                 GimpTextTool    *text_tool);
+static void   gimp_text_tool_preedit_end        (GtkIMContext    *context,
+                                                 GimpTextTool    *text_tool);
+static void   gimp_text_tool_preedit_changed    (GtkIMContext    *context,
                                                  GimpTextTool    *text_tool);
 
 
@@ -94,8 +98,14 @@ gimp_text_tool_editor_init (GimpTextTool *text_tool)
   g_signal_connect (text_tool->im_context, "commit",
                     G_CALLBACK (gimp_text_tool_commit_cb),
                     text_tool);
+  g_signal_connect (text_tool->im_context, "preedit-start",
+                    G_CALLBACK (gimp_text_tool_preedit_start),
+                    text_tool);
+  g_signal_connect (text_tool->im_context, "preedit-end",
+                    G_CALLBACK (gimp_text_tool_preedit_end),
+                    text_tool);
   g_signal_connect (text_tool->im_context, "preedit-changed",
-                    G_CALLBACK (gimp_text_tool_preedit_changed_cb),
+                    G_CALLBACK (gimp_text_tool_preedit_changed),
                     text_tool);
 
 }
@@ -270,38 +280,57 @@ gimp_text_tool_reset_im_context (GimpTextTool *text_tool)
     }
 }
 
-gchar *
-gimp_text_tool_editor_get_text (GimpTextTool *text_tool)
+void
+gimp_text_tool_editor_get_cursor_rect (GimpTextTool   *text_tool,
+                                       PangoRectangle *cursor_rect,
+                                       gint           *logical_off_x,
+                                       gint           *logical_off_y)
 {
-  GtkTextBuffer *buffer = text_tool->text_buffer;
-  GtkTextIter    start, end;
-  GtkTextIter    selstart, selend;
-  gchar         *string;
-  gchar         *fb;
-  gchar         *lb;
+  GtkTextBuffer  *buffer = text_tool->text_buffer;
+  PangoLayout    *layout;
+  PangoRectangle  ink_extents;
+  PangoRectangle  logical_extents;
+  GtkTextIter     start;
+  GtkTextIter     cursor;
+  gint            cursor_index;
+  gchar          *string;
 
-  gtk_text_buffer_get_bounds (buffer, &start, &end);
-  gtk_text_buffer_get_selection_bounds (buffer, &selstart, &selend);
+  g_return_if_fail (GIMP_IS_TEXT_TOOL (text_tool));
+  g_return_if_fail (cursor_rect != NULL);
 
-  fb = gtk_text_buffer_get_text (buffer, &start, &selstart, TRUE);
-  lb = gtk_text_buffer_get_text (buffer, &selstart, &end, TRUE);
+  if (! text_tool->layout)
+    gimp_text_tool_update_layout (text_tool);
 
-  if (text_tool->preedit_string)
-    {
-      if (fb == NULL)
-        string = g_strconcat (text_tool->preedit_string, lb, NULL);
-      else
-        string = g_strconcat (fb, text_tool->preedit_string, lb, NULL);
-    }
+  layout = gimp_text_layout_get_pango_layout (text_tool->layout);
+
+  pango_layout_get_pixel_extents (layout, &ink_extents, &logical_extents);
+  gimp_text_layout_transform_rect (text_tool->layout, &logical_extents);
+
+  if (ink_extents.x < 0)
+    *logical_off_x = -ink_extents.x;
   else
-    {
-      string = g_strconcat (fb, lb, NULL);
-    }
+    *logical_off_x = 0;
+
+  if (ink_extents.y < 0)
+    *logical_off_y = -ink_extents.y;
+  else
+    *logical_off_y = 0;
+
+  gtk_text_buffer_get_start_iter (buffer, &start);
+  gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+                                    gtk_text_buffer_get_insert (buffer));
+
+  string = gtk_text_buffer_get_text (buffer, &start, &cursor, FALSE);
+  cursor_index = strlen (string);
+  g_free (string);
 
-  g_free (fb);
-  g_free (lb);
+  pango_layout_index_to_pos (layout, cursor_index, cursor_rect);
+  gimp_text_layout_transform_rect (text_tool->layout, cursor_rect);
 
-  return string;
+  cursor_rect->x      = PANGO_PIXELS (cursor_rect->x) + *logical_off_x;
+  cursor_rect->y      = PANGO_PIXELS (cursor_rect->y) + *logical_off_y;
+  cursor_rect->width  = PANGO_PIXELS (cursor_rect->width);
+  cursor_rect->height = PANGO_PIXELS (cursor_rect->height);
 }
 
 
@@ -856,8 +885,57 @@ gimp_text_tool_commit_cb (GtkIMContext *context,
 }
 
 static void
-gimp_text_tool_preedit_changed_cb (GtkIMContext *context,
-                                   GimpTextTool *text_tool)
+gimp_text_tool_preedit_start (GtkIMContext *context,
+                              GimpTextTool *text_tool)
+{
+  GimpTool         *tool  = GIMP_TOOL (text_tool);
+  GimpDisplayShell *shell = gimp_display_get_shell (tool->display);
+  GtkWidget        *frame;
+  PangoRectangle    cursor_rect = { 0, };
+  gint              unused1, unused2;
+  gint              off_x, off_y;
+
+  if (text_tool->text)
+    gimp_text_tool_editor_get_cursor_rect (text_tool, &cursor_rect,
+                                           &unused1, &unused2);
+
+  g_object_get (text_tool, "x1", &off_x, "y1", &off_y, NULL);
+
+  text_tool->preedit_overlay = gtk_frame_new (NULL);
+  gtk_frame_set_shadow_type (GTK_FRAME (text_tool->preedit_overlay),
+                             GTK_SHADOW_OUT);
+  gimp_display_shell_add_overlay (GIMP_DISPLAY_SHELL (shell),
+                                  text_tool->preedit_overlay,
+                                  cursor_rect.x + off_x,
+                                  cursor_rect.y + off_y);
+  gtk_widget_show (text_tool->preedit_overlay);
+
+  frame = gtk_frame_new (NULL);
+  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+  gtk_container_add (GTK_CONTAINER (text_tool->preedit_overlay), frame);
+  gtk_widget_show (frame);
+
+  text_tool->preedit_label = gtk_label_new (NULL);
+  gtk_misc_set_padding (GTK_MISC (text_tool->preedit_label), 2, 2);
+  gtk_container_add (GTK_CONTAINER (frame), text_tool->preedit_label);
+  gtk_widget_show (text_tool->preedit_label);
+}
+
+static void
+gimp_text_tool_preedit_end (GtkIMContext *context,
+                            GimpTextTool *text_tool)
+{
+  if (text_tool->preedit_overlay)
+    {
+      gtk_widget_destroy (text_tool->preedit_overlay);
+      text_tool->preedit_overlay = NULL;
+      text_tool->preedit_label   = NULL;
+    }
+}
+
+static void
+gimp_text_tool_preedit_changed (GtkIMContext *context,
+                                GimpTextTool *text_tool)
 {
   if (text_tool->preedit_string)
     g_free (text_tool->preedit_string);
@@ -866,9 +944,7 @@ gimp_text_tool_preedit_changed_cb (GtkIMContext *context,
                                      &text_tool->preedit_string, NULL,
                                      &text_tool->preedit_cursor);
 
-  /* FIXME: call gimp_text_tool_update_layout() here, and make sure
-   * the preedit string is *only* honored for the display, and never
-   * ends up on the text object
-   */
-  gimp_text_tool_update_proxy (text_tool);
+  if (text_tool->preedit_label)
+    gtk_label_set_text (GTK_LABEL (text_tool->preedit_label),
+                        text_tool->preedit_string);
 }
diff --git a/app/tools/gimptexttool-editor.h b/app/tools/gimptexttool-editor.h
index b3d4b03..bb997a5 100644
--- a/app/tools/gimptexttool-editor.h
+++ b/app/tools/gimptexttool-editor.h
@@ -24,22 +24,25 @@
 #define __GIMP_TEXT_TOOL_EDITOR_H__
 
 
-void       gimp_text_tool_editor_init        (GimpTextTool *text_tool);
-void       gimp_text_tool_editor_finalize    (GimpTextTool *text_tool);
+void       gimp_text_tool_editor_init            (GimpTextTool   *text_tool);
+void       gimp_text_tool_editor_finalize        (GimpTextTool   *text_tool);
 
-void       gimp_text_tool_editor_start       (GimpTextTool *text_tool);
-void       gimp_text_tool_editor_halt        (GimpTextTool *text_tool);
+void       gimp_text_tool_editor_start           (GimpTextTool   *text_tool);
+void       gimp_text_tool_editor_halt            (GimpTextTool   *text_tool);
 
-gboolean   gimp_text_tool_editor_key_press   (GimpTextTool *text_tool,
-                                              GdkEventKey  *kevent,
-                                              GimpDisplay  *display);
-gboolean   gimp_text_tool_editor_key_release (GimpTextTool *text_tool,
-                                              GdkEventKey  *kevent,
-                                              GimpDisplay  *display);
+gboolean   gimp_text_tool_editor_key_press       (GimpTextTool   *text_tool,
+                                                  GdkEventKey    *kevent,
+                                                  GimpDisplay    *display);
+gboolean   gimp_text_tool_editor_key_release     (GimpTextTool   *text_tool,
+                                                  GdkEventKey    *kevent,
+                                                  GimpDisplay    *display);
 
-void       gimp_text_tool_reset_im_context   (GimpTextTool *text_tool);
+void       gimp_text_tool_reset_im_context       (GimpTextTool   *text_tool);
 
-gchar    * gimp_text_tool_editor_get_text    (GimpTextTool *text_tool);
+void       gimp_text_tool_editor_get_cursor_rect (GimpTextTool   *text_tool,
+                                                  PangoRectangle *cursor_rect,
+                                                  gint           *logical_off_x,
+                                                  gint           *logical_off_y);
 
 
 #endif /* __GIMP_TEXT_TOOL_EDITOR_H__ */
diff --git a/app/tools/gimptexttool.c b/app/tools/gimptexttool.c
index 1e2edd5..295b353 100644
--- a/app/tools/gimptexttool.c
+++ b/app/tools/gimptexttool.c
@@ -120,9 +120,6 @@ static GimpUIManager * gimp_text_tool_get_popup (GimpTool          *tool,
                                                  const gchar      **ui_path);
 
 static void      gimp_text_tool_draw            (GimpDrawTool      *draw_tool);
-static void      gimp_text_tool_draw_preedit    (GimpDrawTool      *draw_tool,
-                                                 gint               logical_off_x,
-                                                 gint               logical_off_y);
 static void      gimp_text_tool_draw_selection  (GimpDrawTool      *draw_tool,
                                                  gint               logical_off_x,
                                                  gint               logical_off_y);
@@ -829,11 +826,9 @@ gimp_text_tool_draw (GimpDrawTool *draw_tool)
 {
   GimpTextTool   *text_tool = GIMP_TEXT_TOOL (draw_tool);
   GtkTextBuffer  *buffer    = text_tool->text_buffer;
-  gint            logical_off_x = 0;
-  gint            logical_off_y = 0;
-  PangoLayout    *layout;
-  PangoRectangle  ink_extents;
-  PangoRectangle  logical_extents;
+  PangoRectangle  cursor_rect;
+  gint            logical_offset_x;
+  gint            logical_offset_y;
 
   g_object_set (text_tool,
                 "narrow-mode", TRUE,
@@ -846,20 +841,8 @@ gimp_text_tool_draw (GimpDrawTool *draw_tool)
       ! text_tool->layer->text)
     return;
 
-  /* There will be no layout if the function is called from the wrong place */
-  if (! text_tool->layout)
-    gimp_text_tool_update_layout (text_tool);
-
-  layout = gimp_text_layout_get_pango_layout (text_tool->layout);
-
-  pango_layout_get_pixel_extents (layout, &ink_extents, &logical_extents);
-  gimp_text_layout_transform_rect (text_tool->layout, &logical_extents);
-
-  if (ink_extents.x < 0)
-    logical_off_x = -ink_extents.x;
-
-  if (ink_extents.y < 0)
-    logical_off_y = -ink_extents.y;
+  gimp_text_tool_editor_get_cursor_rect (text_tool, &cursor_rect,
+                                         &logical_offset_x, &logical_offset_y);
 
   if (gtk_text_buffer_get_has_selection (buffer))
     {
@@ -884,7 +867,8 @@ gimp_text_tool_draw (GimpDrawTool *draw_tool)
 
       gimp_draw_tool_set_clip_rect (draw_tool, &clip_rect, FALSE);
 
-      gimp_text_tool_draw_selection (draw_tool, logical_off_x, logical_off_y);
+      gimp_text_tool_draw_selection (draw_tool,
+                                     logical_offset_x, logical_offset_y);
 
       /* Turn off clipping when done */
       gimp_draw_tool_set_clip_rect (draw_tool, NULL, FALSE);
@@ -893,148 +877,18 @@ gimp_text_tool_draw (GimpDrawTool *draw_tool)
     {
       /* If the text buffer has no selection, draw the text cursor */
 
-      GtkTextIter     start;
-      GtkTextIter     cursor;
-      gint            cursor_index;
-      PangoRectangle  crect;
-      gchar          *string;
-      gboolean        overwrite_cursor;
-
-      gtk_text_buffer_get_start_iter (buffer, &start);
-      gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
-                                        gtk_text_buffer_get_insert (buffer));
-
-      string = gtk_text_buffer_get_text (buffer, &start, &cursor, FALSE);
-
-      /* Using strlen to get the byte index, not the character offset */
-      cursor_index = strlen (string);
-
-      /* TODO: make cursor position itself even inside preedits! */
-      if (text_tool->preedit_string)
-        cursor_index += strlen (text_tool->preedit_string);
-
-      g_free (string);
-
-      pango_layout_index_to_pos (layout, cursor_index, &crect);
-      gimp_text_layout_transform_rect (text_tool->layout, &crect);
-
-      crect.x      = PANGO_PIXELS (crect.x) + logical_off_x;
-      crect.y      = PANGO_PIXELS (crect.y) + logical_off_y;
-      crect.width  = PANGO_PIXELS (crect.width);
-      crect.height = PANGO_PIXELS (crect.height);
-
-      overwrite_cursor = text_tool->overwrite_mode && crect.width > 0;
+      gboolean overwrite = text_tool->overwrite_mode && cursor_rect.width > 0;
 
       gimp_draw_tool_draw_text_cursor (draw_tool,
-                                       crect.x, crect.y,
-                                       overwrite_cursor ?
-                                       crect.x + crect.width : crect.x,
-                                       crect.y + crect.height,
-                                       overwrite_cursor,
+                                       cursor_rect.x,
+                                       cursor_rect.y,
+                                       overwrite ?
+                                       cursor_rect.x + cursor_rect.width :
+                                       cursor_rect.x,
+                                       cursor_rect.y + cursor_rect.height,
+                                       overwrite,
                                        TRUE);
-
-      if (text_tool->preedit_string)
-        gimp_text_tool_draw_preedit (draw_tool, logical_off_x, logical_off_y);
-    }
-}
-
-static void
-gimp_text_tool_draw_preedit (GimpDrawTool *draw_tool,
-                             gint          logical_off_x,
-                             gint          logical_off_y)
-{
-  GimpTextTool    *text_tool = GIMP_TEXT_TOOL (draw_tool);
-  PangoLayout     *layout;
-  PangoLayoutIter *line_iter;
-  GtkTextIter      cursor, start;
-  gint             min, max;
-  gchar           *string;
-  gint             firstline, lastline;
-  gint             first_x,   last_x;
-  gdouble          first_tmp, last_tmp;
-  gint             i;
-
-  gtk_text_buffer_get_selection_bounds (text_tool->text_buffer, &cursor, NULL);
-  gtk_text_buffer_get_start_iter (text_tool->text_buffer, &start);
-
-  string = gtk_text_buffer_get_text (text_tool->text_buffer,
-                                     &start, &cursor, FALSE);
-  min = strlen (string);
-  g_free (string);
-
-  max = min + strlen (text_tool->preedit_string);
-
-  layout = gimp_text_layout_get_pango_layout (text_tool->layout);
-
-  pango_layout_index_to_line_x (layout, min, 0, &firstline, &first_x);
-  pango_layout_index_to_line_x (layout, max, 0, &lastline,  &last_x);
-
-  first_tmp = first_x;
-  last_tmp  = last_x;
-
-  gimp_text_layout_transform_distance (text_tool->layout, &first_tmp, NULL);
-  gimp_text_layout_transform_distance (text_tool->layout, &last_tmp,  NULL);
-
-  first_x = PANGO_PIXELS (first_tmp) + logical_off_x;
-  last_x  = PANGO_PIXELS (last_tmp)  + logical_off_x;
-
-  line_iter = pango_layout_get_iter (layout);
-  i = 0;
-
-  do
-    {
-      if (i >= firstline && i <= lastline)
-        {
-          PangoRectangle crect;
-
-          pango_layout_iter_get_line_extents (line_iter, NULL, &crect);
-          pango_extents_to_pixels (&crect, NULL);
-
-          gimp_text_layout_transform_rect (text_tool->layout, &crect);
-
-          crect.x += logical_off_x;
-          crect.y += logical_off_y;
-
-          gimp_draw_tool_draw_line (draw_tool,
-                                    crect.x, crect.y + crect.height,
-                                    crect.x + crect.width,
-                                    crect.y + crect.height,
-                                    TRUE);
-
-          if (i == firstline)
-            {
-              PangoRectangle crect2 = crect;
-
-              crect2.width = first_x - crect.x;
-              crect2.x     = crect.x;
-
-              gimp_draw_tool_draw_line (draw_tool,
-                                        crect2.x, crect2.y + crect2.height,
-                                        crect2.width,
-                                        crect2.y + crect2.height,
-                                        TRUE);
-            }
-
-          if (i == lastline)
-            {
-              PangoRectangle crect2 = crect;
-
-              crect2.width = crect.x + crect.width - last_x;
-              crect2.x     = last_x;
-
-              gimp_draw_tool_draw_line (draw_tool,
-                                        crect2.x, crect2.y + crect2.height,
-                                        crect2.x + crect2.width,
-                                        crect2.y + crect2.height,
-                                        TRUE);
-            }
-        }
-
-      i++;
     }
-  while (pango_layout_iter_next_line (line_iter));
-
-  pango_layout_iter_free (line_iter);
 }
 
 static void
@@ -1534,14 +1388,22 @@ gimp_text_tool_create_layer (GimpTextTool *text_tool,
     }
   else
     {
-      gchar *str = gimp_text_tool_editor_get_text (text_tool);
+      GtkTextIter  start;
+      GtkTextIter  end;
+      gchar       *string;
+
+      gtk_text_buffer_get_start_iter (text_tool->text_buffer, &start);
+      gtk_text_buffer_get_end_iter   (text_tool->text_buffer, &end);
+
+      string = gtk_text_buffer_get_text (text_tool->text_buffer,
+                                         &start, &end, FALSE);
 
       g_object_set (text_tool->proxy,
-                    "text",     str,
+                    "text",     string,
                     "box-mode", GIMP_TEXT_BOX_DYNAMIC,
                     NULL);
 
-      g_free (str);
+      g_free (string);
 
       text = gimp_config_duplicate (GIMP_CONFIG (text_tool->proxy));
     }
@@ -1819,7 +1681,15 @@ gimp_text_tool_update_proxy (GimpTextTool *text_tool)
 {
   if (text_tool->text)
     {
-      gchar *string = gimp_text_tool_editor_get_text (text_tool);
+      GtkTextIter  start;
+      GtkTextIter  end;
+      gchar       *string;
+
+      gtk_text_buffer_get_start_iter (text_tool->text_buffer, &start);
+      gtk_text_buffer_get_end_iter   (text_tool->text_buffer, &end);
+
+      string = gtk_text_buffer_get_text (text_tool->text_buffer,
+                                         &start, &end, FALSE);
 
       g_object_set (text_tool->proxy,
                     "text", string,
diff --git a/app/tools/gimptexttool.h b/app/tools/gimptexttool.h
index b7ea7d2..ef9c39f 100644
--- a/app/tools/gimptexttool.h
+++ b/app/tools/gimptexttool.h
@@ -73,6 +73,9 @@ struct _GimpTextTool
   GtkIMContext   *im_context;
   gboolean        needs_im_reset;
 
+  GtkWidget      *preedit_overlay;
+  GtkWidget      *preedit_label;
+
   gchar          *preedit_string;
   gint            preedit_cursor;
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]