[gtk+/touch-text-selection: 2/6] Implement touch text selection in GtkEntry



commit c5c17c5bad6fa65dc36e92e77a4a708fc482d039
Author: Carlos Garnacho <carlos lanedo com>
Date:   Wed Jul 11 15:55:00 2012 +0200

    Implement touch text selection in GtkEntry
    
    GtkTextHandle is used to indicate both the cursor position
    and the selection bound, dragging the handles will modify
    the selection and scroll if necessary.

 gtk/gtkentry.c |  230 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 210 insertions(+), 20 deletions(-)
---
diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c
index ef58c2e..d7b2df1 100644
--- a/gtk/gtkentry.c
+++ b/gtk/gtkentry.c
@@ -65,6 +65,7 @@
 #include "gtkicontheme.h"
 #include "gtkwidgetprivate.h"
 #include "gtkstylecontextprivate.h"
+#include "gtktexthandleprivate.h"
 
 #include "a11y/gtkentryaccessible.h"
 
@@ -160,6 +161,8 @@ struct _GtkEntryPrivate
 
   gchar        *placeholder_text;
 
+  GtkTextHandle *text_handle;
+
   gfloat        xalign;
 
   gint          ascent;                     /* font ascent in pango units  */
@@ -215,6 +218,8 @@ struct _GtkEntryPrivate
   guint         select_words            : 1;
   guint         select_lines            : 1;
   guint         truncate_multiline      : 1;
+  guint         dragging_handle         : 1;
+  guint         dragged_handle_pos      : 1;
 };
 
 struct _EntryIconInfo
@@ -574,6 +579,19 @@ static GdkPixbuf *  gtk_entry_ensure_pixbuf            (GtkEntry             *en
 static void         gtk_entry_update_cached_style_values(GtkEntry      *entry);
 static gboolean     get_middle_click_paste             (GtkEntry *entry);
 
+/* GtkTextHandle handlers */
+static void         gtk_entry_handle_cursor_changed           (GtkTextHandle         *handle,
+                                                               gint                   x,
+                                                               gint                   y,
+                                                               GtkEntry              *entry);
+static void         gtk_entry_handle_selection_bound_changed  (GtkTextHandle         *handle,
+                                                               gint                   x,
+                                                               gint                   y,
+                                                               GtkEntry              *entry);
+static void         gtk_entry_handle_drag_finished            (GtkTextHandle         *handle,
+                                                               GtkEntry              *entry);
+
+
 /* Completion */
 static gint         gtk_entry_completion_timeout       (gpointer            data);
 static gboolean     gtk_entry_completion_key_press     (GtkWidget          *widget,
@@ -2493,6 +2511,15 @@ gtk_entry_init (GtkEntry *entry)
   gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
 
   gtk_entry_update_cached_style_values (entry);
+
+  priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (entry));
+  g_signal_connect (priv->text_handle, "cursor-changed",
+                    G_CALLBACK (gtk_entry_handle_cursor_changed), entry);
+  g_signal_connect (priv->text_handle, "selection-bound-changed",
+                    G_CALLBACK (gtk_entry_handle_selection_bound_changed),
+                    entry);
+  g_signal_connect (priv->text_handle, "drag-finished",
+                    G_CALLBACK (gtk_entry_handle_drag_finished), entry);
 }
 
 static void
@@ -2730,6 +2757,7 @@ gtk_entry_finalize (GObject *object)
   if (priv->recompute_idle)
     g_source_remove (priv->recompute_idle);
 
+  g_object_unref (priv->text_handle);
   g_free (priv->placeholder_text);
   g_free (priv->im_module);
 
@@ -2950,6 +2978,9 @@ gtk_entry_unmap (GtkWidget *widget)
   EntryIconInfo *icon_info = NULL;
   gint i;
 
+  _gtk_text_handle_set_mode (priv->text_handle,
+                             GTK_TEXT_HANDLE_MODE_NONE);
+
   for (i = 0; i < MAX_ICONS; i++)
     {
       if ((icon_info = priv->icons[i]) != NULL)
@@ -3023,7 +3054,7 @@ gtk_entry_realize (GtkWidget *widget)
 
   gtk_entry_adjust_scroll (entry);
   gtk_entry_update_primary_selection (entry);
-
+  _gtk_text_handle_set_relative_to (priv->text_handle, priv->text_area);
 
   /* If the icon positions are already setup, create their windows.
    * Otherwise if they don't exist yet, then construct_icon_info()
@@ -3051,6 +3082,7 @@ gtk_entry_unrealize (GtkWidget *widget)
   gtk_entry_reset_layout (entry);
   
   gtk_im_context_set_client_window (priv->im_context, NULL);
+  _gtk_text_handle_set_relative_to (priv->text_handle, NULL);
 
   clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_PRIMARY);
   if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry))
@@ -3792,7 +3824,75 @@ in_selection (GtkEntry *entry,
   g_free (ranges);
   return retval;
 }
-	      
+
+static void
+_gtk_entry_move_handle (GtkEntry              *entry,
+                        GtkTextHandlePosition  pos,
+                        gint                   x,
+                        gint                   y)
+{
+  GtkEntryPrivate *priv = entry->priv;
+
+  if ((!priv->dragging_handle ||
+       priv->dragged_handle_pos != pos) &&
+      (x < 0 || x > gdk_window_get_width (priv->text_area)))
+    _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE);
+  else
+    {
+      x = CLAMP (x, 0, gdk_window_get_width (priv->text_area));
+      _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
+      _gtk_text_handle_set_position (priv->text_handle, pos, x, y);
+    }
+}
+
+static gint
+_gtk_entry_get_selection_bound_location (GtkEntry *entry)
+{
+  GtkEntryPrivate *priv = entry->priv;
+  PangoLayout *layout;
+  PangoRectangle pos;
+  gint x;
+
+  layout = gtk_entry_ensure_layout (entry, FALSE);
+  pango_layout_index_to_pos (layout, priv->selection_bound, &pos);
+
+  if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL)
+    x = (pos.x + pos.width) / PANGO_SCALE;
+  else
+    x = pos.x / PANGO_SCALE;
+
+  return x;
+}
+
+static void
+_gtk_entry_update_handles (GtkEntry             *entry,
+                           GtkTextHandleMode     mode)
+{
+  GtkEntryPrivate *priv = entry->priv;
+  gint strong_x, x, y;
+
+  y = gdk_window_get_height (priv->text_area);
+  _gtk_text_handle_set_mode (priv->text_handle, mode);
+
+  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+    {
+      gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL);
+      x = strong_x - priv->scroll_offset;
+      _gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR, x, y);
+
+      x = _gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset;
+      _gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_BOUND,
+                              x, y);
+    }
+  else
+    {
+      gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL);
+      x = strong_x - priv->scroll_offset;
+      _gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR,
+                              x, y);
+    }
+}
+
 static gint
 gtk_entry_button_press (GtkWidget      *widget,
 			GdkEventButton *event)
@@ -3938,7 +4038,11 @@ gtk_entry_button_press (GtkWidget      *widget,
 	      priv->drag_start_y = event->y;
 	    }
 	  else
-            gtk_editable_set_position (editable, tmp_pos);
+            {
+              gtk_editable_set_position (editable, tmp_pos);
+              priv->dragged_handle_pos = GTK_TEXT_HANDLE_POSITION_CURSOR;
+              _gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
+            }
 	  break;
  
 	case GDK_2BUTTON_PRESS:
@@ -4030,7 +4134,7 @@ gtk_entry_button_release (GtkWidget      *widget,
       gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x);
 
       gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos);
-
+      _gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
       priv->in_drag = 0;
     }
 
@@ -4126,6 +4230,9 @@ gtk_entry_motion_notify (GtkWidget      *widget,
           gchar *text = NULL;
           cairo_surface_t *surface;
 
+          _gtk_text_handle_set_mode (priv->text_handle,
+                                     GTK_TEXT_HANDLE_MODE_NONE);
+
           gtk_target_list_add_text_targets (target_list, 0);
 
           text = _gtk_entry_get_selected_text (entry);
@@ -4199,6 +4306,8 @@ gtk_entry_motion_notify (GtkWidget      *widget,
 	}
       else
         gtk_entry_set_positions (entry, tmp_pos, -1);
+
+      _gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
     }
       
   return TRUE;
@@ -4240,6 +4349,8 @@ gtk_entry_key_press (GtkWidget   *widget,
 
   gtk_entry_reset_blink_time (entry);
   gtk_entry_pend_cursor_blink (entry);
+  _gtk_text_handle_set_mode (priv->text_handle,
+                             GTK_TEXT_HANDLE_MODE_NONE);
 
   if (priv->editable)
     {
@@ -4344,6 +4455,9 @@ gtk_entry_focus_out (GtkWidget     *widget,
   GtkEntryCompletion *completion;
   GdkKeymap *keymap;
 
+  _gtk_text_handle_set_mode (priv->text_handle,
+                             GTK_TEXT_HANDLE_MODE_NONE);
+
   gtk_widget_queue_draw (widget);
 
   keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
@@ -5960,6 +6074,64 @@ gtk_entry_draw_cursor (GtkEntry  *entry,
     }
 }
 
+
+
+static void
+gtk_entry_handle_cursor_changed (GtkTextHandle *handle,
+                                 gint           x,
+                                 gint           y,
+                                 GtkEntry      *entry)
+{
+  GtkEntryPrivate *priv = entry->priv;
+  GtkTextHandleMode mode;
+  gint tmp_pos;
+
+  mode = _gtk_text_handle_get_mode (handle);
+  tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset);
+
+  priv->dragging_handle = TRUE;
+  priv->dragged_handle_pos = GTK_TEXT_HANDLE_POSITION_CURSOR;
+
+  if (tmp_pos != priv->current_pos)
+    {
+      if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
+        gtk_entry_set_positions (entry, tmp_pos, tmp_pos);
+      else
+        gtk_entry_set_positions (entry, tmp_pos, priv->selection_bound);
+
+      _gtk_entry_update_handles (entry, mode);
+    }
+}
+
+static void
+gtk_entry_handle_selection_bound_changed (GtkTextHandle *handle,
+                                          gint           x,
+                                          gint           y,
+                                          GtkEntry      *entry)
+{
+  GtkEntryPrivate *priv = entry->priv;
+  gint tmp_pos;
+
+  tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset);
+  priv->dragging_handle = TRUE;
+  priv->dragged_handle_pos = GTK_TEXT_HANDLE_POSITION_SELECTION_BOUND;
+
+  /* Manipulate the cursor position as it's now been moved here */
+  if (tmp_pos != priv->selection_bound)
+    {
+      gtk_entry_set_positions (entry, priv->current_pos, tmp_pos);
+      _gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
+    }
+}
+
+static void
+gtk_entry_handle_drag_finished (GtkTextHandle *handle,
+                                GtkEntry      *entry)
+{
+  GtkEntryPrivate *priv = entry->priv;
+  priv->dragging_handle = FALSE;
+}
+
 void
 _gtk_entry_reset_im_context (GtkEntry *entry)
 {
@@ -6134,6 +6306,7 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
   PangoLayout *layout;
   PangoLayoutLine *line;
   PangoRectangle logical_rect;
+  GtkTextHandleMode handle_mode;
 
   if (!gtk_widget_get_realized (GTK_WIDGET (entry)))
     return;
@@ -6170,22 +6343,34 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
 
   priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset);
 
-  /* And make sure cursors are on screen. Note that the cursor is
-   * actually drawn one pixel into the INNER_BORDER space on
-   * the right, when the scroll is at the utmost right. This
-   * looks better to to me than confining the cursor inside the
-   * border entirely, though it means that the cursor gets one
-   * pixel closer to the edge of the widget on the right than
-   * on the left. This might need changing if one changed
-   * INNER_BORDER from 2 to 1, as one would do on a
-   * small-screen-real-estate display.
-   *
-   * We always make sure that the strong cursor is on screen, and
-   * put the weak cursor on screen if possible.
-   */
+  if (priv->dragging_handle &&
+      priv->dragged_handle_pos == GTK_TEXT_HANDLE_POSITION_SELECTION_BOUND)
+    {
+      /* The text handle corresponding to the selection bound is
+       * being dragged, ensure it stays onscreen even if we scroll
+       * cursors away, this is so both handles can cause content
+       * to scroll.
+       */
+      strong_x = weak_x = _gtk_entry_get_selection_bound_location (entry);
+    }
+  else
+    {
+      /* And make sure cursors are on screen. Note that the cursor is
+       * actually drawn one pixel into the INNER_BORDER space on
+       * the right, when the scroll is at the utmost right. This
+       * looks better to to me than confining the cursor inside the
+       * border entirely, though it means that the cursor gets one
+       * pixel closer to the edge of the widget on the right than
+       * on the left. This might need changing if one changed
+       * INNER_BORDER from 2 to 1, as one would do on a
+       * small-screen-real-estate display.
+       *
+       * We always make sure that the strong cursor is on screen, and
+       * put the weak cursor on screen if possible.
+       */
+      gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
+    }
 
-  gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
-  
   strong_xoffset = strong_x - priv->scroll_offset;
 
   if (strong_xoffset < 0)
@@ -6212,6 +6397,11 @@ gtk_entry_adjust_scroll (GtkEntry *entry)
     }
 
   g_object_notify (G_OBJECT (entry), "scroll-offset");
+
+  handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
+
+  if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
+    _gtk_entry_update_handles (entry, handle_mode);
 }
 
 static void
@@ -6234,7 +6424,7 @@ gtk_entry_move_adjustments (GtkEntry *entry)
 
   gtk_widget_get_allocation (widget, &allocation);
 
-  /* Cursor position, layout offset, border width, and widget allocation */
+  /* Cursor/char position, layout offset, border width, and widget allocation */
   gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &x, NULL);
   get_layout_position (entry, &layout_x, NULL);
   _gtk_entry_get_borders (entry, &borders);



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