--- /home/tojonjic/cvs/gtk+/gtk/gtktextview.c	2004-08-30 15:16:39.000000000 +0200
+++ gtktextview.c	2004-09-26 13:52:34.327773392 +0200
@@ -420,6 +420,34 @@ static GtkTargetEntry target_table[] = {
 static GtkContainerClass *parent_class = NULL;
 static guint signals[LAST_SIGNAL] = { 0 };
 
+
+typedef enum 
+{
+  SELECT_CHARACTERS,
+  SELECT_WORDS,
+  SELECT_LINES
+} SelectionGranularity;
+
+
+typedef struct
+{
+  gdouble x_offset;
+  gdouble y_offset;
+} AutoScrollInfo;
+
+
+typedef struct _GtkTextViewPrivate GtkTextViewPrivate;
+
+struct _GtkTextViewPrivate
+{
+  AutoScrollInfo autoscroll;
+
+  SelectionGranularity granularity;
+};
+
+#define GTK_TEXT_VIEW_GET_PRIVATE(text_view) (G_TYPE_INSTANCE_GET_PRIVATE ((text_view), GTK_TYPE_TEXT_VIEW, GtkTextViewPrivate))
+
+
 GType
 gtk_text_view_get_type (void)
 {
@@ -1034,6 +1062,8 @@ gtk_text_view_class_init (GtkTextViewCla
   gtk_binding_entry_add_signal (binding_set, GDK_KP_Tab, GDK_SHIFT_MASK | GDK_CONTROL_MASK,
 				"move_focus", 1,
 				GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD);
+
+  g_type_class_add_private (gobject_class, sizeof (GtkTextViewPrivate));
 }
 
 static void
@@ -2462,7 +2492,7 @@ gtk_text_view_get_cursor_visible    (Gtk
  * @text_view: a #GtkTextView
  *
  * Moves the cursor to the currently visible region of the
- * buffer, it it isn't there already.
+ * buffer, if it isn't there already.
  *
  * Return value: TRUE if the cursor had to be moved.
  **/
@@ -5373,105 +5403,6 @@ gtk_text_view_unselect (GtkTextView *tex
                              &insert);
 }
 
-static void
-move_mark_to_pointer_and_scroll (GtkTextView *text_view,
-                                 const gchar *mark_name)
-{
-  gint x, y;
-  GdkModifierType state;
-  GtkTextIter newplace;
-
-  /*   DV(g_print (G_STRLOC": begin\n")); */
-  
-  gdk_window_get_pointer (text_view->text_window->bin_window,
-                          &x, &y, &state);
-
-  /*   DV(g_print (G_STRLOC": get iter at pixel\n"); */
-  gtk_text_layout_get_iter_at_pixel (text_view->layout,
-                                     &newplace,
-                                     x + text_view->xoffset,
-                                     y + text_view->yoffset);
-
-  {
-    GtkTextMark *mark =
-      gtk_text_buffer_get_mark (get_buffer (text_view), mark_name);
-
-    /* This may invalidate the layout */
-    DV(g_print (G_STRLOC": move mark\n"));
-    gtk_text_buffer_move_mark (get_buffer (text_view),
-                               mark,
-                               &newplace);
-
-    DV(g_print (G_STRLOC": scrolling onscreen\n"));
-    gtk_text_view_scroll_mark_onscreen (text_view, mark);
-  }
-
-  DV (g_print ("first validate idle leaving %s is %d\n",
-               G_STRLOC, text_view->first_validate_idle));
-}
-
-static gint
-selection_scan_timeout (gpointer data)
-{
-  GtkTextView *text_view;
-
-  GDK_THREADS_ENTER ();
-  
-  text_view = GTK_TEXT_VIEW (data);
-
-  DV(g_print (G_STRLOC": calling move_mark_to_pointer_and_scroll\n"));
-  gtk_text_view_scroll_mark_onscreen (text_view, 
-				      gtk_text_buffer_get_mark (get_buffer (text_view),
-								"insert"));
-
-  GDK_THREADS_LEAVE ();
-  
-  return TRUE; /* remain installed. */
-}
-
-#define DND_SCROLL_MARGIN 0.20
-
-static gint
-drag_scan_timeout (gpointer data)
-{
-  GtkTextView *text_view;
-  gint x, y;
-  GdkModifierType state;
-  GtkTextIter newplace;
-
-  GDK_THREADS_ENTER ();
-  
-  text_view = GTK_TEXT_VIEW (data);
-
-  gdk_window_get_pointer (text_view->text_window->bin_window,
-                          &x, &y, &state);
-  
-  gtk_text_layout_get_iter_at_pixel (text_view->layout,
-                                     &newplace,
-                                     x + text_view->xoffset,
-                                     y + text_view->yoffset);
-  
-  gtk_text_buffer_move_mark (get_buffer (text_view),
-                             text_view->dnd_mark,
-                             &newplace);
-
-  DV(g_print (G_STRLOC": scrolling onscreen\n"));
-  gtk_text_view_scroll_to_mark (text_view,
-                                text_view->dnd_mark,
-                                DND_SCROLL_MARGIN, FALSE, 0.0, 0.0);
-
-  GDK_THREADS_LEAVE ();
-  
-  return TRUE;
-}
-
-typedef enum 
-{
-  SELECT_CHARACTERS,
-  SELECT_WORDS,
-  SELECT_LINES
-} SelectionGranularity;
-
 /*
  * Move @start and @end to the boundaries of the selection unit (indicated by 
  * @granularity) which contained @start initially. Return wether @start was
@@ -5527,36 +5458,54 @@ extend_selection (GtkTextView *text_view
   
   return extend;
 }
- 
-static gint
-selection_motion_event_handler (GtkTextView *text_view, GdkEventMotion *event, gpointer data)
+
+/*
+ * Move the selection marks with respect to the current pointer location
+ * and chosen granularity.
+ */
+static void
+update_selection_bounds (GtkTextView *text_view)
 {
-  SelectionGranularity granularity = GPOINTER_TO_INT (data);
+  GtkTextViewPrivate *private;
+  GtkTextIter start;
+  GdkModifierType state;
+  GdkRectangle text_rect;
+  gint x, y;
+  SelectionGranularity granularity;
+  GtkTextBuffer *buffer;
+
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
 
-  if (granularity == SELECT_CHARACTERS) 
+  gdk_window_get_pointer (text_view->text_window->bin_window,
+                          &x, &y, &state);
+
+  text_rect = text_view->text_window->allocation;
+
+  /* Adjust x, y to be in text window boundary. */
+  x = MAX (0, MIN (x, text_rect.width - 1));
+  y = MAX (0, MIN (y, text_rect.height - 1));
+
+  gtk_text_layout_get_iter_at_pixel (text_view->layout,
+                                     &start,
+                                     x + text_view->xoffset,
+                                     y + text_view->yoffset);
+
+  buffer = get_buffer (text_view);
+  granularity = private->granularity;
+
+  if (granularity == SELECT_CHARACTERS)
     {
-      move_mark_to_pointer_and_scroll (text_view, "insert");
+      gtk_text_buffer_move_mark (get_buffer (text_view),
+                                 gtk_text_buffer_get_insert (buffer),
+                                 &start);
     }
-  else 
+  else
     {
-      gint x, y;
-      GdkModifierType state;
-      GtkTextIter start, end;
-      GtkTextIter old_start, old_end;    
-      GtkTextIter ins, bound;    
-      GtkTextBuffer *buffer;
-      
-      buffer = get_buffer (text_view);
+      GtkTextIter end;
+      GtkTextIter old_start, old_end;
+      GtkTextIter ins, bound;
 
-      gdk_window_get_pointer (text_view->text_window->bin_window,
-			      &x, &y, &state);
-      
-      gtk_text_layout_get_iter_at_pixel (text_view->layout,
-					 &start,
-					 event->x + text_view->xoffset,
-					 event->y + text_view->yoffset); 
-      
-      if (extend_selection (text_view, granularity, &start, &end)) 
+      if (extend_selection (text_view, granularity, &start, &end))
 	{
 	  /* Extend selection */
 	  gtk_text_buffer_get_iter_at_mark (buffer, 
@@ -5565,7 +5514,6 @@ selection_motion_event_handler (GtkTextV
 	  gtk_text_buffer_get_iter_at_mark (buffer, 
 					    &bound,
 					    gtk_text_buffer_get_selection_bound (buffer));
-
 	  if (gtk_text_iter_compare (&ins, &bound) < 0) 
 	    {
 	      old_start = ins;
@@ -5601,32 +5549,326 @@ selection_motion_event_handler (GtkTextV
 	      /* newly selected unit inside the current selection 
 		 at the end */
 	      if (!gtk_text_iter_equal (&ins, &end)) 
-		  ins = start;
+		ins = start;
 	    }
 
 	  gtk_text_buffer_select_range (buffer, &ins, &bound);
 	}
-
-      gtk_text_view_scroll_mark_onscreen (text_view, 
-					  gtk_text_buffer_get_mark (buffer,
-								    "insert"));
     }
+}
+
+static gint
+selection_scan_timeout (gpointer data)
+{
+  GtkTextView *text_view;
+  GtkTextViewPrivate *private;
+  GtkAdjustment *vadj, *hadj;
+
+  GDK_THREADS_ENTER ();
+
+  text_view = (GtkTextView *) data;
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
+
+  vadj = get_vadjustment (text_view);
+  hadj = get_hadjustment (text_view);
 
+  set_adjustment_clamped (hadj,
+                          hadj->value + private->autoscroll.x_offset);
+  set_adjustment_clamped (vadj,
+                          vadj->value + private->autoscroll.y_offset);
   
-  /* If we had to scroll offscreen, insert a timeout to do so
-   * again. Note that in the timeout, even if the mouse doesn't
-   * move, due to this scroll xoffset/yoffset will have changed
-   * and we'll need to scroll again.
-   */
-  if (text_view->scroll_timeout != 0) /* reset on every motion event */
-    g_source_remove (text_view->scroll_timeout);
+  update_selection_bounds (text_view);
+
+  GDK_THREADS_LEAVE ();
+
+  return TRUE; /* remain installed. */
+}
+
+
+/* Constants for DND auto scrolling.
+ *
+ * For a given delta, the scrolling offset is simply an interpolated 
+ * value in the range [DND_SCROLL_MIN_SPEED, DND_SCROLL_MAX_SPEED]
+ */
+
+/* The dnd auto scroll margin is the minimum of 1) window height
+ * (or width) x DND_SCROLL_MARGIN_PERC and 2) DND_SCROLL_MARGIN_MAX */
+#define DND_SCROLL_MARGIN_MAX    65
+#define DND_SCROLL_MARGIN_PERC  .20
+
+/* Turn off auto scrolling  for extremely small widths. */
+#define DND_SCROLL_MARGIN_MIN     5
+
+/* The speed at margin inner and outer boundaries, repectively. */
+#define DND_SCROLL_MIN_SPEED    1.0
+#define DND_SCROLL_MAX_SPEED   50.0
+
+#define DND_SCROLL_TIMEOUT_INT   50
+
+static gboolean
+compute_dnd_auto_scroll_velocity (GtkTextView *text_view)
+{
+  GtkTextViewPrivate *private;
+  GdkRectangle text_rect;
+  gdouble x_margin, y_margin, max_vel;
+  gint x, y;
+  GdkModifierType state;
+
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
+
+  gdk_window_get_pointer (text_view->text_window->bin_window,
+                          &x, &y, &state);
   
-  text_view->scroll_timeout =
-    g_timeout_add (50, selection_scan_timeout, text_view);
+  text_rect = text_view->text_window->allocation;
+
+  x_margin = text_rect.width * DND_SCROLL_MARGIN_PERC;
+  x_margin = MIN (x_margin, DND_SCROLL_MARGIN_MAX);
+
+  y_margin = text_rect.height * DND_SCROLL_MARGIN_PERC;
+  y_margin = MIN (y_margin, DND_SCROLL_MARGIN_MAX);
+
+  /* Set up scrolling offsets. */
+
+  max_vel = DND_SCROLL_MAX_SPEED - DND_SCROLL_MIN_SPEED;
+
+  if (x_margin >= DND_SCROLL_MARGIN_MIN)
+    {
+      gdouble vel = 0.0;
+
+      if (0 <= x && x < x_margin)
+        {
+          vel = (x_margin - x) / x_margin;
+          vel = vel * vel;
+          vel = - (DND_SCROLL_MIN_SPEED + vel * max_vel);
+        }
+      else if (text_rect.width > x &&
+               x > (text_rect.width-1) - x_margin)
+        {
+          vel = (x_margin - (text_rect.width-1 - x)) / x_margin;
+          vel = vel * vel;
+          vel = DND_SCROLL_MIN_SPEED + vel * max_vel;
+        }
+
+      private->autoscroll.x_offset = vel;
+    }
+  else
+    private->autoscroll.x_offset = 0;
+
+  if (y_margin >= DND_SCROLL_MARGIN_MIN)
+    {
+      gdouble vel = 0.0;
+
+      if (0 <= y && y < y_margin)
+        {
+          vel = (y_margin - y) / y_margin;
+          vel = vel * vel;
+          vel = - (DND_SCROLL_MIN_SPEED + vel * max_vel);
+        }
+      else if (text_rect.height > y &&
+               y > (text_rect.height-1) - y_margin)
+        {
+          vel = (y_margin - (text_rect.height-1 - y)) / y_margin;
+          vel = vel * vel;
+          vel = DND_SCROLL_MIN_SPEED + vel * max_vel;
+        }
+
+      private->autoscroll.y_offset = vel;
+    }
+  else
+    private->autoscroll.y_offset = 0;
+
+  return (private->autoscroll.x_offset != 0.0 || 
+          private->autoscroll.y_offset != 0.0);
+}
+
+static gint
+drag_scan_timeout (gpointer data)
+{
+  GtkAdjustment *hadj, *vadj;
+  GtkTextView *text_view;
+  gint x, y;
+  GdkModifierType state;
+  GtkTextViewPrivate *private;
+
+  GDK_THREADS_ENTER ();
+
+  text_view = GTK_TEXT_VIEW (data);
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
+
+  compute_dnd_auto_scroll_velocity (text_view);
+
+  hadj = get_hadjustment (text_view);
+  vadj = get_vadjustment (text_view);
+
+  set_adjustment_clamped (hadj, 
+                          hadj->value + private->autoscroll.x_offset);
+  set_adjustment_clamped (vadj, 
+                          vadj->value + private->autoscroll.y_offset);
+
+  gdk_window_get_pointer (text_view->text_window->bin_window,
+                          &x, &y, &state);
+
+  if (x >= 0 &&
+      x < text_view->text_window->allocation.width &&
+      y >= 0 &&
+      y < text_view->text_window->allocation.height)
+    {
+      GtkTextIter drop_place;
+
+      gtk_text_layout_get_iter_at_pixel (text_view->layout,
+                                         &drop_place,
+                                         x + text_view->xoffset,
+                                         y + text_view->yoffset);
+
+      gtk_text_buffer_move_mark (get_buffer (text_view),
+                                 text_view->dnd_mark,
+                                 &drop_place);
+    }
+
+  GDK_THREADS_LEAVE ();
 
   return TRUE;
 }
 
+/*
+ * Constants for the selection drag automatic scrolling code. These are  
+ * supposed to be sensible values, though there is probably no *obvious* 
+ * choice for any of them.
+ *
+ * For selection, the scrolling velocity consists of a constant term, a
+ * linear acceleration per pixel, and an exponential one.
+ */
+
+/* The constant part, in pixels. */
+#define SEL_SCROLL_MIN_SPEED      2
+
+/* The constant acceleration, pixels/pixel. */
+#define SEL_SCROLL_LIN_ACCEL     .8
+
+/* The exponential acceleration "factor" */
+#define SEL_SCROLL_EXP_ACCEL   .045
+
+/* The threshold on which we stop accelerating. */
+#define SEL_SCROLL_ACCEL_MARGIN 450
+
+#define SEL_SCROLL_TIMEOUT_INT   50
+
+static gboolean
+selection_motion_event_handler (GtkWidget      *widget,
+                                GdkEventMotion *event,
+                                gpointer        data)
+{
+  GtkTextView *text_view;
+  GtkTextViewPrivate *private;
+  gint x, y;
+  GdkModifierType state;
+  GdkRectangle text_rect;
+  SelectionGranularity granularity;
+
+  gboolean trigger_scroll = FALSE;
+
+  text_view = GTK_TEXT_VIEW (widget);
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
+
+  granularity = GPOINTER_TO_INT (data);
+  private->granularity = granularity;
+
+  update_selection_bounds (text_view);
+
+  /*
+   * Check if the pointer is offscreen and if so, calculate the 
+   * scrolling velocity and install an auto scroll timeout handler. 
+   */
+
+  gdk_window_get_pointer (text_view->text_window->bin_window,
+                          &x, &y, &state);
+
+  text_rect = text_view->text_window->allocation;
+
+  if (x < 0 || x >= text_rect.width)
+    {
+      gdouble delta, vel, expvel;
+
+      if (x < 0)
+        {
+          delta = - x;
+          vel = -1.0;
+        }
+      else
+        {
+          delta = x - text_rect.width + 1;
+          vel = 1.0;
+        }
+
+      delta = MIN (delta, SEL_SCROLL_ACCEL_MARGIN);
+
+      expvel = delta * SEL_SCROLL_EXP_ACCEL;
+      expvel = expvel * expvel;
+
+      vel *= (SEL_SCROLL_MIN_SPEED +
+              (delta * SEL_SCROLL_LIN_ACCEL) + expvel);
+
+      private->autoscroll.x_offset = vel;
+
+      trigger_scroll = TRUE;
+    }
+  else
+    private->autoscroll.x_offset = 0;
+
+  if (y < 0 || y >= text_rect.height)
+    {
+      gdouble delta, vel, expvel;
+
+      if (y < 0)
+        {
+          delta = - y;
+          vel = -1.0;
+        }
+      else
+        {
+          delta = y - text_rect.height + 1;
+          vel = 1.0;
+	}
+
+      delta = MIN (delta, SEL_SCROLL_ACCEL_MARGIN);
+
+      expvel = delta * SEL_SCROLL_EXP_ACCEL;
+      expvel *= expvel;
+
+      vel *= (SEL_SCROLL_MIN_SPEED +
+              (delta * SEL_SCROLL_LIN_ACCEL) + expvel);
+
+      private->autoscroll.y_offset = vel;
+
+      trigger_scroll = TRUE;
+    }
+  else
+    private->autoscroll.y_offset = 0;
+
+  if (trigger_scroll)
+    {
+      if (text_view->scroll_timeout == 0)
+        {
+          text_view->scroll_timeout = 
+            g_timeout_add (SEL_SCROLL_TIMEOUT_INT,
+                           selection_scan_timeout, text_view);
+        }
+    }
+  else
+    {
+      if (text_view->scroll_timeout != 0)
+        {
+          g_source_remove (text_view->scroll_timeout);
+          text_view->scroll_timeout = 0;
+        }
+     
+      private->autoscroll.y_offset = 
+        private->autoscroll.x_offset = 0;
+    }
+  
+  return TRUE;
+}
+ 
 static void
 gtk_text_view_start_selection_drag (GtkTextView       *text_view,
                                     const GtkTextIter *iter,
@@ -5650,7 +5892,7 @@ gtk_text_view_start_selection_drag (GtkT
   buffer = get_buffer (text_view);
   
   start = *iter;
-  
+
   extend_selection (text_view, granularity, &start, &end);
 
   if (button->state & GDK_SHIFT_MASK)
@@ -5670,10 +5912,11 @@ gtk_text_view_start_selection_drag (GtkT
 
   gtk_text_buffer_select_range (buffer, &end, &start);
 
-  text_view->selection_drag_handler = g_signal_connect (text_view,
-                                                        "motion_notify_event",
-                                                        G_CALLBACK (selection_motion_event_handler),
-                                                        GINT_TO_POINTER (granularity));
+  text_view->selection_drag_handler = 
+    g_signal_connect (text_view,
+		      "motion_notify_event",
+		      G_CALLBACK (selection_motion_event_handler),
+		      GINT_TO_POINTER (granularity));
 }
 
 /* returns whether we were really dragging */
@@ -6041,13 +6284,15 @@ gtk_text_view_drag_motion (GtkWidget    
 {
   GtkTextIter newplace;
   GtkTextView *text_view;
+  GtkTextViewPrivate *private;
   GtkTextIter start;
   GtkTextIter end;
   GdkRectangle target_rect;
   gint bx, by;
   GdkDragAction suggested_action = 0;
-  
+
   text_view = GTK_TEXT_VIEW (widget);
+  private = GTK_TEXT_VIEW_GET_PRIVATE (text_view);
 
   target_rect = text_view->text_window->allocation;
   
@@ -6120,16 +6365,27 @@ gtk_text_view_drag_motion (GtkWidget    
                              text_view->dnd_mark,
                              &newplace);
 
-  DV(g_print (G_STRLOC": scrolling to mark\n"));
-  gtk_text_view_scroll_to_mark (text_view,
-                                text_view->dnd_mark,
-                                DND_SCROLL_MARGIN, FALSE, 0.0, 0.0);
-  
-  if (text_view->scroll_timeout != 0) /* reset on every motion event */
-    g_source_remove (text_view->scroll_timeout);
-      
-  text_view->scroll_timeout =
-    g_timeout_add (50, drag_scan_timeout, text_view);
+  /* Install an auto scroll timeout handler, if in scroll margin. */
+
+  if (compute_dnd_auto_scroll_velocity (text_view))
+    {
+      if (text_view->scroll_timeout == 0)
+        {
+          text_view->scroll_timeout =
+            g_timeout_add (DND_SCROLL_TIMEOUT_INT, drag_scan_timeout, text_view);
+        }
+    }
+  else
+    {
+      if (text_view->scroll_timeout != 0)
+        {
+          g_source_remove (text_view->scroll_timeout);
+          text_view->scroll_timeout = 0;
+        }
+     
+      private->autoscroll.y_offset = 
+        private->autoscroll.x_offset = 0;
+    }
 
   /* TRUE return means don't propagate the drag motion to parent
    * widgets that may also be drop sites.
