Index: gtktreeprivate.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeprivate.h,v
retrieving revision 1.69
diff -u -p -r1.69 gtktreeprivate.h
--- gtktreeprivate.h	24 Jan 2006 10:49:29 -0000	1.69
+++ gtktreeprivate.h	15 Feb 2006 11:06:02 -0000
@@ -58,6 +58,13 @@ enum
   DRAG_COLUMN_WINDOW_STATE_ARROW_RIGHT = 4
 };
 
+enum
+{
+  RUBBERBAND_OFF = 0,
+  RUBBERBAND_MAYBE_START = 1,
+  RUBBERBAND_ACTIVE = 2
+};
+
 #define GTK_TREE_VIEW_SET_FLAG(tree_view, flag)   G_STMT_START{ (tree_view->priv->flags|=flag); }G_STMT_END
 #define GTK_TREE_VIEW_UNSET_FLAG(tree_view, flag) G_STMT_START{ (tree_view->priv->flags&=~(flag)); }G_STMT_END
 #define GTK_TREE_VIEW_FLAG_SET(tree_view, flag)   ((tree_view->priv->flags&flag)==flag)
@@ -171,6 +178,18 @@ struct _GtkTreeViewPrivate
   gint pressed_button;
   gint press_start_x;
   gint press_start_y;
+
+  gint rubberband_status;
+  gint rubberband_x;
+  gint rubberband_y;
+  gint rubberband_shift;
+  gint rubberband_ctrl;
+
+  GtkRBNode *rubberband_start_node;
+  GtkRBTree *rubberband_start_tree;
+
+  GtkRBNode *rubberband_end_node;
+  GtkRBTree *rubberband_end_tree;
 
   /* fixed height */
   gint fixed_height;
Index: gtktreeview.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeview.c,v
retrieving revision 1.518
diff -u -p -r1.518 gtktreeview.c
--- gtktreeview.c	6 Feb 2006 18:32:59 -0000	1.518
+++ gtktreeview.c	15 Feb 2006 11:06:03 -0000
@@ -379,6 +379,7 @@ static void     column_sizing_notify    
                                                               gpointer           data);
 static gboolean expand_collapse_timeout                      (gpointer           data);
 static gboolean do_expand_collapse                           (GtkTreeView       *tree_view);
+static void     gtk_tree_view_stop_rubberband                (GtkTreeView       *tree_view);
 
 /* interactive search */
 static void     gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view);
@@ -455,6 +456,8 @@ static void gtk_tree_view_tree_window_to
 						      gint        *tx,
 						      gint        *ty);
 
+static gint scroll_row_timeout                       (gpointer     data);
+static void remove_scroll_timeout                    (GtkTreeView *tree_view);
 
 static GtkContainerClass *parent_class = NULL;
 static guint tree_view_signals [LAST_SIGNAL] = { 0 };
@@ -2246,6 +2249,7 @@ gtk_tree_view_button_press (GtkWidget   
       gint column_handled_click = FALSE;
       gboolean row_double_click = FALSE;
       gboolean rtl;
+      gboolean node_selected;
 
       /* Empty tree? */
       if (tree_view->priv->tree == NULL)
@@ -2406,6 +2410,7 @@ gtk_tree_view_button_press (GtkWidget   
 	}
 
       /* select */
+      node_selected = GTK_RBNODE_FLAG_SET (node, GTK_RBNODE_IS_SELECTED);
       pre_val = tree_view->priv->vadjustment->value;
 
       /* we only handle selection modifications on the first button press
@@ -2458,6 +2463,20 @@ gtk_tree_view_button_press (GtkWidget   
           tree_view->priv->pressed_button = event->button;
           tree_view->priv->press_start_x = event->x;
           tree_view->priv->press_start_y = event->y;
+
+	  if (!node_selected
+	      && tree_view->priv->selection->type == GTK_SELECTION_MULTIPLE)
+	    {
+	      tree_view->priv->press_start_y += tree_view->priv->dy;
+	      tree_view->priv->rubberband_x = event->x;
+	      tree_view->priv->rubberband_y = event->y + tree_view->priv->dy;
+	      tree_view->priv->rubberband_status = RUBBERBAND_MAYBE_START;
+
+	      if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
+		tree_view->priv->rubberband_ctrl = TRUE;
+	      if ((event->state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK)
+		tree_view->priv->rubberband_shift = TRUE;
+	    }
         }
 
       /* Test if a double click happened on the same row. */
@@ -2673,6 +2692,9 @@ gtk_tree_view_button_release (GtkWidget 
   if (GTK_TREE_VIEW_FLAG_SET (tree_view, GTK_TREE_VIEW_IN_COLUMN_DRAG))
     return gtk_tree_view_button_release_drag_column (widget, event);
 
+  if (tree_view->priv->rubberband_status)
+    gtk_tree_view_stop_rubberband (tree_view);
+
   if (tree_view->priv->pressed_button == event->button)
     tree_view->priv->pressed_button = -1;
 
@@ -3369,6 +3391,342 @@ gtk_tree_view_motion_drag_column (GtkWid
   return TRUE;
 }
 
+static void
+gtk_tree_view_stop_rubberband (GtkTreeView *tree_view)
+{
+  remove_scroll_timeout (tree_view);
+  gtk_grab_remove (GTK_WIDGET (tree_view));
+
+  gtk_widget_queue_draw (GTK_WIDGET (tree_view));
+
+  if (tree_view->priv->rubberband_status == RUBBERBAND_ACTIVE)
+    {
+      GtkTreePath *tmp_path;
+
+      /* The anchor path should be set to the start path */
+      tmp_path = _gtk_tree_view_find_path (tree_view,
+					   tree_view->priv->rubberband_start_tree,
+					   tree_view->priv->rubberband_start_node);
+
+      if (tree_view->priv->anchor)
+	gtk_tree_row_reference_free (tree_view->priv->anchor);
+
+      tree_view->priv->anchor =
+	gtk_tree_row_reference_new_proxy (G_OBJECT (tree_view),
+					  tree_view->priv->model,
+					  tmp_path);
+
+      gtk_tree_path_free (tmp_path);
+
+      /* ... and the cursor to the end path */
+      tmp_path = _gtk_tree_view_find_path (tree_view,
+					   tree_view->priv->rubberband_end_tree,
+					   tree_view->priv->rubberband_end_node);
+      gtk_tree_view_real_set_cursor (GTK_TREE_VIEW (tree_view), tmp_path, FALSE, FALSE);
+      gtk_tree_path_free (tmp_path);
+
+      _gtk_tree_selection_emit_changed (tree_view->priv->selection);
+    }
+
+  /* Clear status variables */
+  tree_view->priv->rubberband_status = RUBBERBAND_OFF;
+  tree_view->priv->rubberband_shift = 0;
+  tree_view->priv->rubberband_ctrl = 0;
+
+  tree_view->priv->rubberband_start_node = NULL;
+  tree_view->priv->rubberband_start_tree = NULL;
+  tree_view->priv->rubberband_end_node = NULL;
+  tree_view->priv->rubberband_end_tree = NULL;
+}
+
+static void
+gtk_tree_view_update_rubberband_selection_range (GtkTreeView *tree_view,
+						 GtkRBTree   *start_tree,
+						 GtkRBNode   *start_node,
+						 GtkRBTree   *end_tree,
+						 GtkRBNode   *end_node,
+						 gboolean     select,
+						 gboolean     skip_start,
+						 gboolean     skip_end)
+{
+  if (start_node == end_node)
+    return;
+
+  /* We skip the first node and jump inside the loop */
+  if (skip_start)
+    goto skip_first;
+
+  do
+    {
+      if (select)
+        {
+	  if (tree_view->priv->rubberband_shift)
+	    GTK_RBNODE_SET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	  else if (tree_view->priv->rubberband_ctrl)
+	    {
+	      /* Toggle the selection state */
+	      if (GTK_RBNODE_FLAG_SET (start_node, GTK_RBNODE_IS_SELECTED))
+		GTK_RBNODE_UNSET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	      else
+		GTK_RBNODE_SET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	    }
+	  else
+	    GTK_RBNODE_SET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	}
+      else
+        {
+	  /* Mirror the above */
+	  if (tree_view->priv->rubberband_shift)
+	    GTK_RBNODE_UNSET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	  else if (tree_view->priv->rubberband_ctrl)
+	    {
+	      /* Toggle the selection state */
+	      if (GTK_RBNODE_FLAG_SET (start_node, GTK_RBNODE_IS_SELECTED))
+		GTK_RBNODE_UNSET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	      else
+		GTK_RBNODE_SET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	    }
+	  else
+	    GTK_RBNODE_UNSET_FLAG (start_node, GTK_RBNODE_IS_SELECTED);
+	}
+
+      _gtk_tree_view_queue_draw_node (tree_view, start_tree, start_node, NULL);
+
+      if (start_node == end_node)
+	break;
+
+skip_first:
+
+      if (start_node->children)
+        {
+	  start_tree = start_node->children;
+	  start_node = start_tree->root;
+	  while (start_node->left != start_tree->nil)
+	    start_node = start_node->left;
+	}
+      else
+        {
+	  _gtk_rbtree_next_full (start_tree, start_node, &start_tree, &start_node);
+
+	  if (!start_tree)
+	    /* Ran out of tree */
+	    break;
+	}
+
+      if (skip_end && start_node == end_node)
+	break;
+    }
+  while (TRUE);
+}
+
+static void
+gtk_tree_view_update_rubberband_selection (GtkTreeView *tree_view)
+{
+  GtkRBTree *start_tree, *end_tree;
+  GtkRBNode *start_node, *end_node;
+
+  _gtk_rbtree_find_offset (tree_view->priv->tree, MIN (tree_view->priv->press_start_y, tree_view->priv->rubberband_y), &start_tree, &start_node);
+  _gtk_rbtree_find_offset (tree_view->priv->tree, MAX (tree_view->priv->press_start_y, tree_view->priv->rubberband_y), &end_tree, &end_node);
+
+  /* Handle the start area first */
+  if (!tree_view->priv->rubberband_start_node)
+    {
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       start_tree,
+						       start_node,
+						       end_tree,
+						       end_node,
+						       TRUE,
+						       FALSE,
+						       FALSE);
+    }
+  else if (_gtk_rbtree_node_find_offset (start_tree, start_node) <
+           _gtk_rbtree_node_find_offset (tree_view->priv->rubberband_start_tree, tree_view->priv->rubberband_start_node))
+    {
+      /* New node is above the old one; selection became bigger */
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       start_tree,
+						       start_node,
+						       tree_view->priv->rubberband_start_tree,
+						       tree_view->priv->rubberband_start_node,
+						       TRUE,
+						       FALSE,
+						       TRUE);
+    }
+  else if (_gtk_rbtree_node_find_offset (start_tree, start_node) >
+           _gtk_rbtree_node_find_offset (tree_view->priv->rubberband_start_tree, tree_view->priv->rubberband_start_node))
+    {
+      /* New node is below the old one; selection became smaller */
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       tree_view->priv->rubberband_start_tree,
+						       tree_view->priv->rubberband_start_node,
+						       start_tree,
+						       start_node,
+						       FALSE,
+						       FALSE,
+						       TRUE);
+    }
+
+  tree_view->priv->rubberband_start_tree = start_tree;
+  tree_view->priv->rubberband_start_node = start_node;
+
+  /* Next, handle the end area */
+  if (!tree_view->priv->rubberband_end_node)
+    {
+      /* In the event this happens, start_node was also NULL; this case is
+       * handled above.
+       */
+    }
+  else if (!end_node)
+    {
+      /* Find the last node in the tree */
+      _gtk_rbtree_find_offset (tree_view->priv->tree, tree_view->priv->height - 1,
+			       &end_tree, &end_node);
+
+      /* Selection reached end of the tree */
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       tree_view->priv->rubberband_end_tree,
+						       tree_view->priv->rubberband_end_node,
+						       end_tree,
+						       end_node,
+						       TRUE,
+						       TRUE,
+						       FALSE);
+    }
+  else if (_gtk_rbtree_node_find_offset (end_tree, end_node) >
+           _gtk_rbtree_node_find_offset (tree_view->priv->rubberband_end_tree, tree_view->priv->rubberband_end_node))
+    {
+      /* New node is below the old one; selection became bigger */
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       tree_view->priv->rubberband_end_tree,
+						       tree_view->priv->rubberband_end_node,
+						       end_tree,
+						       end_node,
+						       TRUE,
+						       TRUE,
+						       FALSE);
+    }
+  else if (_gtk_rbtree_node_find_offset (end_tree, end_node) <
+           _gtk_rbtree_node_find_offset (tree_view->priv->rubberband_end_tree, tree_view->priv->rubberband_end_node))
+    {
+      /* New node is above the old one; selection became smaller */
+      gtk_tree_view_update_rubberband_selection_range (tree_view,
+						       end_tree,
+						       end_node,
+						       tree_view->priv->rubberband_end_tree,
+						       tree_view->priv->rubberband_end_node,
+						       FALSE,
+						       TRUE,
+						       FALSE);
+    }
+
+  tree_view->priv->rubberband_end_tree = end_tree;
+  tree_view->priv->rubberband_end_node = end_node;
+}
+
+static void
+gtk_tree_view_update_rubberband (GtkTreeView *tree_view)
+{
+  gint x, y;
+  GdkRectangle old_area;
+  GdkRectangle new_area;
+  GdkRectangle common;
+  GdkRegion *invalid_region;
+
+  old_area.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubberband_x);
+  old_area.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubberband_y) - tree_view->priv->dy;
+  old_area.width = ABS (tree_view->priv->rubberband_x - tree_view->priv->press_start_x) + 1;
+  old_area.height = ABS (tree_view->priv->rubberband_y - tree_view->priv->press_start_y) + 1;
+
+  gdk_window_get_pointer (tree_view->priv->bin_window, &x, &y, NULL);
+
+  x = MAX (x, 0);
+  y = MAX (y, 0) + tree_view->priv->dy;
+
+  new_area.x = MIN (tree_view->priv->press_start_x, x);
+  new_area.y = MIN (tree_view->priv->press_start_y, y) - tree_view->priv->dy;
+  new_area.width = ABS (x - tree_view->priv->press_start_x) + 1;
+  new_area.height = ABS (y - tree_view->priv->press_start_y) + 1;
+
+  invalid_region = gdk_region_rectangle (&old_area);
+  gdk_region_union_with_rect (invalid_region, &new_area);
+
+  gdk_rectangle_intersect (&old_area, &new_area, &common);
+  if (common.width > 2 && common.height > 2)
+    {
+      GdkRegion *common_region;
+
+      /* make sure the border is invalidated */
+      common.x += 1;
+      common.y += 1;
+      common.width -= 2;
+      common.height -= 2;
+
+      common_region = gdk_region_rectangle (&common);
+
+      gdk_region_subtract (invalid_region, common_region);
+      gdk_region_destroy (common_region);
+    }
+
+  gdk_window_invalidate_region (tree_view->priv->bin_window, invalid_region, TRUE);
+
+  gdk_region_destroy (invalid_region);
+
+  tree_view->priv->rubberband_x = x;
+  tree_view->priv->rubberband_y = y;
+
+  gtk_tree_view_update_rubberband_selection (tree_view);
+}
+
+static void
+gtk_tree_view_paint_rubberband (GtkTreeView  *tree_view,
+				GdkRectangle *area)
+{
+  cairo_t *cr;
+  GdkRectangle rect;
+  GdkRectangle rubber_rect;
+
+  rubber_rect.x = MIN (tree_view->priv->press_start_x, tree_view->priv->rubberband_x);
+  rubber_rect.y = MIN (tree_view->priv->press_start_y, tree_view->priv->rubberband_y) - tree_view->priv->dy;
+  rubber_rect.width = ABS (tree_view->priv->press_start_x - tree_view->priv->rubberband_x) + 1;
+  rubber_rect.height = ABS (tree_view->priv->press_start_y - tree_view->priv->rubberband_y) + 1;
+
+  if (!gdk_rectangle_intersect (&rubber_rect, area, &rect))
+    return;
+
+  cr = gdk_cairo_create (tree_view->priv->bin_window);
+  cairo_set_line_width (cr, 1.0);
+
+  /* FIXME: need a better selection color here */
+  cairo_set_source_rgba (cr,
+			 0.0, 0.0, 0.0,
+      /*
+			 GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].red / 65535.,
+			 GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].green / 65535.,
+			 GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].blue / 65535.,
+			 */
+			 .25);
+
+  gdk_cairo_rectangle (cr, &rect);
+  cairo_clip (cr);
+  cairo_paint (cr);
+
+  /* FIXME: need a better selection color here */
+  cairo_set_source_rgb (cr,
+			0.0, 0.0, 0.0
+      /*
+			GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].red / 65535.,
+			GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].green / 65535.,
+			GTK_WIDGET (tree_view)->style->base[GTK_STATE_SELECTED].blue / 65535.*/);
+
+  cairo_rectangle (cr,
+		   rubber_rect.x + 0.5, rubber_rect.y + 0.5,
+		   rubber_rect.width - 1, rubber_rect.height - 1);
+  cairo_stroke (cr);
+
+  cairo_destroy (cr);
+}
+
 static gboolean
 gtk_tree_view_motion_bin_window (GtkWidget      *widget,
 				 GdkEventMotion *event)
@@ -3383,8 +3741,26 @@ gtk_tree_view_motion_bin_window (GtkWidg
   if (tree_view->priv->tree == NULL)
     return FALSE;
 
+  if (tree_view->priv->rubberband_status == RUBBERBAND_MAYBE_START)
+    {
+      gtk_grab_add (GTK_WIDGET (tree_view));
+      gtk_tree_view_update_rubberband (tree_view);
+
+      tree_view->priv->rubberband_status = RUBBERBAND_ACTIVE;
+    }
+  else if (tree_view->priv->rubberband_status == RUBBERBAND_ACTIVE)
+    {
+      gtk_tree_view_update_rubberband (tree_view);
+
+      if (tree_view->priv->scroll_timeout == 0)
+        {
+	  tree_view->priv->scroll_timeout = g_timeout_add (150, scroll_row_timeout, tree_view);
+	}
+    }
+
   /* only check for an initiated drag when a button is pressed */
-  if (tree_view->priv->pressed_button >= 0)
+  if (tree_view->priv->pressed_button >= 0
+      && !tree_view->priv->rubberband_status)
     gtk_tree_view_maybe_begin_dragging_row (tree_view, event);
 
   new_y = TREE_WINDOW_Y_TO_RBTREE_Y(tree_view, event->y);
@@ -3990,7 +4366,23 @@ gtk_tree_view_bin_expose (GtkWidget     
     }
   while (y_offset < event->area.height);
 
- done:
+done:
+
+ if (tree_view->priv->rubberband_status == RUBBERBAND_ACTIVE)
+   {
+     GdkRectangle *rectangles;
+     gint n_rectangles;
+
+     gdk_region_get_rectangles (event->region,
+				&rectangles,
+				&n_rectangles);
+
+     while (n_rectangles--)
+       gtk_tree_view_paint_rubberband (tree_view, &rectangles[n_rectangles]);
+
+     g_free (rectangles);
+   }
+
   if (cursor_path)
     gtk_tree_path_free (cursor_path);
 
@@ -5767,6 +6159,7 @@ remove_scroll_timeout (GtkTreeView *tree
       tree_view->priv->scroll_timeout = 0;
     }
 }
+
 static gboolean
 check_model_dnd (GtkTreeModel *model,
                  GType         required_iface,
@@ -5847,6 +6240,9 @@ scroll_row_timeout (gpointer data)
 
   gtk_tree_view_vertical_autoscroll (tree_view);
 
+  if (tree_view->priv->rubberband_status == RUBBERBAND_ACTIVE)
+    gtk_tree_view_update_rubberband (tree_view);
+
   GDK_THREADS_LEAVE ();
 
   return TRUE;
@@ -9058,7 +9454,6 @@ gtk_tree_view_real_select_cursor_parent 
 
   return TRUE;
 }
-
 static gboolean
 gtk_tree_view_search_entry_flush_timeout (GtkTreeView *tree_view)
 {
