[egg-list-box/flow-box-enhancements] Add rubberband selection



commit 2ac23a7b22d1550f3f5d35659bf497ae618d9144
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Sep 29 02:05:29 2013 -0400

    Add rubberband selection

 egg-flow-box.c |  285 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 278 insertions(+), 7 deletions(-)
---
diff --git a/egg-flow-box.c b/egg-flow-box.c
index 518647d..92590e2 100644
--- a/egg-flow-box.c
+++ b/egg-flow-box.c
@@ -123,6 +123,7 @@ _egg_marshal_VOID__ENUM_INT (GClosure * closure,
 #define P_(msgid) (msgid)
 
 #define DEFAULT_MAX_CHILDREN_PER_LINE 7
+#define RUBBERBAND_START_DISTANCE 32
 
 enum {
   CHILD_ACTIVATED,
@@ -190,6 +191,13 @@ struct _EggFlowBoxPrivate {
   EggFlowBoxSortFunc sort_func;
   gpointer           sort_data;
   GDestroyNotify     sort_destroy;
+
+  gboolean           track_motion;
+  gboolean           rubberband_select;
+  EggFlowBoxChild   *rubberband_first;
+  EggFlowBoxChild   *rubberband_last;
+  gint               button_down_x;
+  gint               button_down_y;
 };
 
 typedef struct _EggFlowBoxChildPrivate EggFlowBoxChildPrivate;
@@ -1058,8 +1066,9 @@ fit_aligned_item_requests (EggFlowBox     *box,
                                                 n_children,
                                                 sizes);
 
-  /* Try columnizing the whole thing and adding an item to the end of the line;
-   * try to fit as many columns into the available size as possible
+  /* Try columnizing the whole thing and adding an item to the end of
+   * the line; try to fit as many columns into the available size as
+   * possible
    */
   for (try_length = *line_length + 1; try_line_size < avail_size; try_length++)
     {
@@ -2607,6 +2616,7 @@ egg_flow_box_motion_notify_event (GtkWidget      *widget,
                                   GdkEventMotion *event)
 {
   EggFlowBox *box = EGG_FLOW_BOX (widget);
+  EggFlowBoxPrivate *priv = BOX_PRIV (box);
   EggFlowBoxChild *child;
   GdkWindow *window;
   GdkWindow *event_window;
@@ -2634,6 +2644,25 @@ egg_flow_box_motion_notify_event (GtkWidget      *widget,
   egg_flow_box_update_prelight (box, child);
   egg_flow_box_update_active (box, child);
 
+  if (priv->track_motion)
+    {
+      if (!priv->rubberband_select &&
+          (event->x - priv->button_down_x) * (event->x - priv->button_down_x) +
+          (event->y - priv->button_down_y) * (event->y - priv->button_down_y) > RUBBERBAND_START_DISTANCE * 
RUBBERBAND_START_DISTANCE)
+        {
+          priv->rubberband_select = TRUE;
+          priv->rubberband_first = egg_flow_box_find_child_at_pos (box, priv->button_down_x, 
priv->button_down_y);
+        }
+
+      if (priv->rubberband_select)
+        {
+          if (priv->rubberband_first == NULL)
+            priv->rubberband_first = child;
+          if (child != NULL)
+            priv->rubberband_last = child;
+        }
+    }
+
   return FALSE;
 }
 
@@ -2655,7 +2684,20 @@ egg_flow_box_button_press_event (GtkWidget      *widget,
           gtk_widget_queue_draw (GTK_WIDGET (box));
           if (event->type == GDK_2BUTTON_PRESS &&
               !priv->activate_on_single_click)
-            g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+            {
+              g_signal_emit (box, signals[CHILD_ACTIVATED], 0, child);
+              return TRUE;
+            }
+        }
+
+      if (priv->selection_mode == GTK_SELECTION_MULTIPLE)
+        {
+          priv->track_motion = TRUE;
+          priv->rubberband_select = FALSE;
+          priv->rubberband_first = NULL;
+          priv->rubberband_last = NULL;
+          priv->button_down_x = event->x;
+          priv->button_down_y = event->y;
         }
     }
 
@@ -2885,6 +2927,15 @@ egg_flow_box_button_release_event (GtkWidget      *widget,
       gtk_widget_queue_draw (GTK_WIDGET (box));
   }
 
+  priv->track_motion = FALSE;
+  if (priv->rubberband_select)
+    {
+      egg_flow_box_select_all_between (box, priv->rubberband_first, priv->rubberband_last);
+      priv->rubberband_first = NULL;
+      priv->rubberband_last = NULL;
+      priv->rubberband_select = FALSE;
+    }
+
   return FALSE;
 }
 
@@ -3352,12 +3403,129 @@ egg_flow_box_move_cursor (EggFlowBox      *box,
   egg_flow_box_update_selection (box, child, modify_selection, extend_selection);
 }
 
+static void
+path_from_horizontal_line_rects (cairo_t      *cr,
+                                 GdkRectangle *lines,
+                                 gint          n_lines)
+{
+  gint start_line, end_line;
+  GdkRectangle *r;
+  gint i;
+
+  /* Join rows vertically by extending to the middle */
+  for (i = 0; i < n_lines - 1; i++)
+    {
+      GdkRectangle *r1 = &lines[i];
+      GdkRectangle *r2 = &lines[i+1];
+      gint gap, old;
+
+      gap = r2->y - (r1->y + r1->height);
+      r1->height += gap / 2;
+      old = r2->y;
+      r2->y = r1->y + r1->height;
+      r2->height += old - r2->y;
+    }
+
+  cairo_new_path (cr);
+  start_line = 0;
+
+  do
+    {
+      for (i = start_line; i < n_lines; i++)
+        {
+          r = &lines[i];
+          if (i == start_line)
+            cairo_move_to (cr, r->x + r->width, r->y);
+          else
+            cairo_line_to (cr, r->x + r->width, r->y);
+          cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+          if (i < n_lines - 1 &&
+              (r->x + r->width < lines[i+1].x ||
+              r->x > lines[i+1].x + lines[i+1].width))
+            {
+              i++;
+              break;
+            }
+        }
+      end_line = i;
+      for (i = end_line - 1; i >= start_line; i--)
+        {
+          r = &lines[i];
+          cairo_line_to (cr, r->x, r->y + r->height);
+          cairo_line_to (cr, r->x, r->y);
+        }
+      cairo_close_path (cr);
+      start_line = end_line;
+    }
+  while (end_line < n_lines);
+}
+
+static void
+path_from_vertical_line_rects (cairo_t      *cr,
+                               GdkRectangle *lines,
+                               gint          n_lines)
+{
+  gint start_line, end_line;
+  GdkRectangle *r;
+  gint i;
+
+  /* Join rows horizontall by extending to the middle */
+  for (i = 0; i < n_lines - 1; i++)
+    {
+      GdkRectangle *r1 = &lines[i];
+      GdkRectangle *r2 = &lines[i+1];
+      gint gap, old;
+
+      gap = r2->x - (r1->x + r1->width);
+      r1->width += gap / 2;
+      old = r2->x;
+      r2->x = r1->x + r1->width;
+      r2->width += old - r2->x;
+    }
+
+  cairo_new_path (cr);
+  start_line = 0;
+
+  do
+    {
+      for (i = start_line; i < n_lines; i++)
+        {
+          r = &lines[i];
+          if (i == start_line)
+            cairo_move_to (cr, r->x, r->y + r->height);
+          else
+            cairo_line_to (cr, r->x, r->y + r->height);
+          cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+          if (i < n_lines - 1 &&
+              (r->y + r->height < lines[i+1].y ||
+              r->y > lines[i+1].y + lines[i+1].height))
+            {
+              i++;
+              break;
+            }
+        }
+      end_line = i;
+      for (i = end_line - 1; i >= start_line; i--)
+        {
+          r = &lines[i];
+          cairo_line_to (cr, r->x + r->width, r->y);
+          cairo_line_to (cr, r->x, r->y);
+        }
+      cairo_close_path (cr);
+      start_line = end_line;
+    }
+  while (end_line < n_lines);
+}
+
 static gboolean
 egg_flow_box_draw (GtkWidget *widget,
                    cairo_t   *cr)
 {
   EggFlowBox *box = EGG_FLOW_BOX (widget);
-  GtkAllocation allocation = {0};
+  EggFlowBoxPrivate *priv = BOX_PRIV (box);
+  GtkAllocation allocation = { 0, };
   GtkStyleContext* context;
 
   gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
@@ -3366,6 +3534,105 @@ egg_flow_box_draw (GtkWidget *widget,
 
   GTK_WIDGET_CLASS (egg_flow_box_parent_class)->draw (widget, cr);
 
+  if (priv->rubberband_first && priv->rubberband_last)
+    {
+      GSequenceIter *iter, *iter1, *iter2;
+      GdkRectangle line_rect, rect;
+      GArray *lines;
+      gboolean vertical;
+
+      vertical = priv->orientation == GTK_ORIENTATION_VERTICAL;
+
+      cairo_save (cr);
+
+      context = gtk_widget_get_style_context (widget);
+      gtk_style_context_save (context);
+      gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+      iter1 = CHILD_PRIV (priv->rubberband_first)->iter;
+      iter2 = CHILD_PRIV (priv->rubberband_last)->iter;
+
+      if (g_sequence_iter_compare (iter2, iter1) < 0)
+        {
+          iter = iter1;
+          iter1 = iter2;
+          iter2 = iter;
+        }
+
+      line_rect.width = 0;
+      lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+
+      for (iter = iter1;
+           !g_sequence_iter_is_end (iter);
+           iter = g_sequence_iter_next (iter))
+        {
+          GtkWidget *child;
+
+          child = g_sequence_get (iter);
+          gtk_widget_get_allocation (GTK_WIDGET (child), &rect);
+          if (line_rect.width == 0)
+            line_rect = rect;
+          else
+            {
+              if ((vertical && rect.x == line_rect.x) ||
+                  (!vertical && rect.y == line_rect.y))
+                gdk_rectangle_union (&rect, &line_rect, &line_rect);
+              else
+                {
+                  g_array_append_val (lines, line_rect);
+                  line_rect = rect;
+                }
+            }
+
+          if (g_sequence_iter_compare (iter, iter2) == 0)
+            break;
+        }
+
+      if (line_rect.width != 0)
+        g_array_append_val (lines, line_rect);
+
+      if (lines->len > 0)
+        {
+          GtkStateFlags state;
+          cairo_path_t *path;
+          GtkBorder border;
+          GdkRGBA border_color;
+
+          if (vertical)
+            path_from_vertical_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+          else
+            path_from_horizontal_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+
+          /* For some reason we need to copy and reapply the path,
+           * or it gets eaten by gtk_render_background()
+           */
+          path = cairo_copy_path (cr);
+
+          cairo_save (cr);
+          cairo_clip (cr);
+          gtk_widget_get_allocation (widget, &allocation);
+          gtk_render_background (context, cr,
+                                 0, 0,
+                                 allocation.width, allocation.height);
+          cairo_restore (cr);
+
+          cairo_append_path (cr, path);
+          cairo_path_destroy (path);
+
+          state = gtk_widget_get_state_flags (widget);
+          gtk_style_context_get_border_color (context, state, &border_color);
+          gtk_style_context_get_border (context, state, &border);
+
+          cairo_set_line_width (cr, border.left);
+          gdk_cairo_set_source_rgba (cr, &border_color);
+          cairo_stroke (cr);
+        }
+      g_array_free (lines, TRUE);
+
+      gtk_style_context_restore (context);
+      cairo_restore (cr);
+    }
+
   return TRUE;
 }
 
@@ -3385,9 +3652,13 @@ egg_flow_box_realize (GtkWidget *widget)
   attributes.width = allocation.width;
   attributes.height = allocation.height;
   attributes.window_type = GDK_WINDOW_CHILD;
-  attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box)) |
-    GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK |
-    GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
+  attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (box))
+                                | GDK_ENTER_NOTIFY_MASK
+                                | GDK_LEAVE_NOTIFY_MASK
+                                | GDK_POINTER_MOTION_MASK
+                                | GDK_EXPOSURE_MASK
+                                | GDK_BUTTON_PRESS_MASK
+                                | GDK_BUTTON_RELEASE_MASK;
   attributes.wclass = GDK_INPUT_OUTPUT;
 
   window = gdk_window_new (gtk_widget_get_parent_window (GTK_WIDGET (box)),


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