changeset 10262:4face60ac721

(scrolling_1): When scroll_region_ok is set, use a new scrolling method which scrolls groups of lines directly to their final vertical positions. (struct matrix_elt): New field writecount. (calculate_direct_scrolling): New function. (do_direct_scrolling): New function.
author Richard M. Stallman <rms@gnu.org>
date Mon, 26 Dec 1994 15:38:56 +0000
parents 4fd304db9216
children 525b67bc4f17
files src/scroll.c
diffstat 1 files changed, 451 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/src/scroll.c	Mon Dec 26 15:37:22 1994 +0000
+++ b/src/scroll.c	Mon Dec 26 15:38:56 1994 +0000
@@ -52,11 +52,14 @@
     /* Number of deletes so far in this run of deletes,
        for the cost in deletecost.  */
     unsigned char deletecount;
+    /* Number of writes so far since the last insert
+       or delete for the cost in writecost. */
+    unsigned char writecount;
   };
 
 
-/* Determine, in matrix[i,j], the cost of updating the first j old lines
-   into the first i new lines.
+/* Determine, in matrix[i,j], the cost of updating the first j old
+   lines into the first i new lines using the general scrolling method.
    This involves using insert or delete somewhere if i != j.
    For each matrix elements, three kinds of costs are recorded:
    the smallest cost that ends with an insert, the smallest
@@ -217,8 +220,8 @@
       }
 }
 
-/* Perform insert-lines and delete-lines operations on FRAME
-   according to the costs in MATRIX.
+/* Perform insert-lines and delete-lines operations on FRAME according
+   to the costs in MATRIX, using the general scrolling method.
    Update the frame's current_glyphs info to record what was done.
 
    WINDOW_SIZE is the number of lines being considered for scrolling
@@ -361,12 +364,440 @@
     set_terminal_window (0);
 }
 
+/* Determine, in matrix[i,j], the cost of updating the first j
+   old lines into the first i new lines using the direct
+   scrolling method.  When the old line and the new line have
+   different hash codes, the calculated cost of updating old
+   line j into new line i includes the cost of outputting new
+   line i, and if i != j, the cost of outputting the old line j
+   is also included, as a penalty for moving the line and then
+   erasing it.  In addition, the cost of updating a sequence of
+   lines with constant i - j includes the cost of scrolling the
+   old lines into their new positions, unless i == j.  Scrolling
+   is achieved by setting the screen window to avoid affecting
+   other lines below, and inserting or deleting lines at the top
+   of the scrolled region.  The cost of scrolling a sequence of
+   lines includes the fixed cost of specifying a scroll region,
+   plus a variable cost which can depend upon the number of lines
+   involved and the distance by which they are scrolled, and an
+   extra cost to discourage long scrolls.
+
+   As reflected in the matrix, an insert or delete does not
+   correspond directly to the insertion or deletion which is
+   used in scrolling lines.  An insert means that the value of i
+   has increased without a corresponding increase in the value
+   of j.  A delete means that the value of j has increased
+   without a corresponding increase in the value of i.  A write
+   means that i and j are both increased by the same amount, and
+   that the old lines will be moved to their new positions.
+
+   An insert following a delete is allowed only if i > j.
+   A delete following an insert is allowed only if i < j.
+   These restrictions ensure that the new lines in an insert
+   will always be blank as an effect of the neighboring writes.
+   Thus the calculated cost of an insert is simply the cost of
+   outputting the new line contents.  The direct cost of a
+   delete is zero.  Inserts and deletes indirectly affect the
+   total cost through their influence on subsequent writes. */
+
+/* The vectors draw_cost, old_hash, and new_hash have the same
+   meanings here as in calculate_scrolling, and old_draw_cost
+   is the equivalent of draw_cost for the old line contents */
+
+static void
+calculate_direct_scrolling (frame, matrix, window_size, lines_below,
+			    draw_cost, old_draw_cost, old_hash, new_hash,
+			    free_at_end)
+     FRAME_PTR frame;
+     /* matrix is of size window_size + 1 on each side.  */
+     struct matrix_elt *matrix;
+     int window_size;
+     int *draw_cost;
+     int *old_draw_cost;
+     int *old_hash;
+     int *new_hash;
+     int free_at_end;
+{
+  register int i, j;
+  int frame_height = FRAME_HEIGHT (frame);
+  register struct matrix_elt *p, *p1;
+  register int cost, cost1, delta;
+
+  /* first_insert_cost[-I] is the cost of doing the first insert-line
+     at a position I lines above the bottom line in the scroll window. */
+  int *first_insert_cost
+    = &FRAME_INSERT_COST (frame)[frame_height - 1];
+  int *first_delete_cost
+    = &FRAME_DELETE_COST (frame)[frame_height - 1];
+  int *next_insert_cost
+    = &FRAME_INSERTN_COST (frame)[frame_height - 1];
+  int *next_delete_cost
+    = &FRAME_DELETEN_COST (frame)[frame_height - 1];
+
+  int scroll_overhead;
+
+  /* Discourage long scrolls on fast lines.
+     Don't scroll nearly a full frame height unless it saves
+     at least 1/4 second.  */
+  int extra_cost = baud_rate / (10 * 4 * FRAME_HEIGHT (frame));
+
+  if (baud_rate <= 0)
+    extra_cost = 1;
+
+  /* Overhead of setting the scroll window, plus the extra cost
+     cost of scrolling by a distance of one.  The extra cost is
+     added once for consistency with the cost vectors */
+  scroll_overhead = scroll_region_cost + extra_cost;
+
+  /* initialize the top left corner of the matrix */
+  matrix->writecost = 0;
+  matrix->insertcost = INFINITY;
+  matrix->deletecost = INFINITY;
+  matrix->writecount = 0;
+  matrix->insertcount = 0;
+  matrix->deletecount = 0;
+
+  /* initialize the left edge of the matrix */
+  cost = 0;
+  for (i = 1; i <= window_size; i++)
+    {
+      p = matrix + i * (window_size + 1);
+      cost += draw_cost[i];
+      p->insertcost = cost;
+      p->writecost = INFINITY;
+      p->deletecost = INFINITY;
+      p->insertcount = i;
+      p->writecount = 0;
+      p->deletecount = 0;
+    }
+
+  /* initialize the top edge of the matrix */
+  for (j = 1; j <= window_size; j++)
+    {
+      matrix[j].deletecost = 0;
+      matrix[j].writecost = INFINITY;
+      matrix[j].insertcost = INFINITY;
+      matrix[j].deletecount = j;
+      matrix[j].writecount = 0;
+      matrix[j].insertcount = 0;
+    }
+
+  /* `i' represents the vpos among new frame contents.
+     `j' represents the vpos among the old frame contents.  */
+  p = matrix + window_size + 2;	/* matrix [1, 1] */
+
+  for (i = 1; i <= window_size; i++, p++)
+    for (j = 1; j <= window_size; j++, p++)
+      {
+	/* p contains the address of matrix [i, j] */
+
+	/* First calculate the cost assuming we do
+	   not insert or delete above this line.
+	   That is, if we update through line i-1
+	   based on old lines through j-1,
+	   and then just change old line j to new line i.
+
+	   Depending on which choice gives the lower cost,
+	   this usually involves either scrolling a single line
+	   or extending a sequence of scrolled lines, but
+	   when i == j, no scrolling is required. */
+	p1 = p - window_size - 2; /* matrix [i-1, j-1] */
+	cost = p1->insertcost;
+	if (cost > p1->deletecost)
+	  cost = p1->deletecost;
+	cost1 = p1->writecost;
+	if (i == j)
+	  {
+	    if (cost > cost1)
+	      {
+		cost = cost1;
+		p->writecount = p1->writecount + 1;
+	      }
+	    else
+	      p->writecount = 1;
+	    if (old_hash[j] != new_hash[i])
+	      {
+		cost += draw_cost[i];
+	      }
+	  }
+	else
+	  {
+	    if (i > j)
+	      {
+		delta = i - j;
+
+		/* The cost added here for scrolling the first line by
+		   a distance N includes the overhead of setting the
+		   scroll window, the cost of inserting N lines at a
+		   position N lines above the bottom line of the window,
+		   and an extra cost which is proportional to N. */
+		cost += scroll_overhead + first_insert_cost[-delta] +
+		  (delta-1) * (next_insert_cost[-delta] + extra_cost);
+
+		/* In the most general case, the insertion overhead and
+		   the multiply factor can grow linearly as the distance
+		   from the bottom of the window increases.  The incremental
+		   cost of scrolling an additional line depends upon the
+		   rate of change of these two parameters.  Each of these
+		   growth rates can be determined by a simple difference.
+		   To reduce the cumulative effects of rounding error, we
+		   vary the position at which the difference is computed. */
+		cost1 += first_insert_cost[-j] - first_insert_cost[1-j] +
+		  (delta-1) * (next_insert_cost[-j] - next_insert_cost[1-j]); 
+	      }
+	    else
+	      {
+		delta = j - i;
+		cost += scroll_overhead + first_delete_cost[-delta] +
+		  (delta-1) * (next_delete_cost[-delta] + extra_cost);
+		cost1 += first_delete_cost[-i] - first_delete_cost[1-i] +
+		  (delta-1) * ( next_delete_cost[-i] - next_delete_cost[1-i]); 
+	      }
+	    if (cost1 < cost)
+	      {
+		cost = cost1;
+		p->writecount = p1->writecount + 1;
+	      }
+	    else
+	      p->writecount = 1;
+	    if (old_hash[j] != new_hash[i])
+	      {
+		cost += draw_cost[i] + old_draw_cost[j];
+	      }
+	  }
+	p->writecost = cost;
+
+	/* Calculate the cost if we do an insert-line
+	   before outputting this line.
+	   That is, we update through line i-1
+	   based on old lines through j,
+	   do an insert-line on line i,
+	   and then output line i from scratch,
+	   leaving old lines starting from j for reuse below.  */
+	p1 = p - window_size - 1; /* matrix [i-1, j] */
+	cost = p1->writecost;
+	/* If i > j, an insert is allowed after a delete. */
+	if (i > j && p1->deletecost < cost)
+	  cost = p1->deletecost;
+	if (p1->insertcost <= cost)
+	  {
+	    cost = p1->insertcost;
+	    p->insertcount = p1->insertcount + 1;
+	  }
+	else
+	  p->insertcount = 1;
+	cost += draw_cost[i];
+	p->insertcost = cost;
+
+	/* Calculate the cost if we do a delete line after
+	   outputting this line.
+	   That is, we update through line i
+	   based on old lines through j-1,
+	   and throw away old line j.  */
+	p1 = p - 1;		/* matrix [i, j-1] */
+	cost = p1->writecost;
+	/* If i < j, a delete is allowed after an insert. */
+	if (i < j && p1->insertcost < cost)
+	  cost = p1->insertcost;
+	cost1 = p1->deletecost;
+	if (p1->deletecost <= cost)
+	  {
+	    cost = p1->deletecost;
+	    p->deletecount = p1->deletecount + 1;
+	  }
+	else
+	  p->deletecount = 1;
+	p->deletecost = cost;
+      }
+}
+
+/* Perform insert-lines and delete-lines operations on FRAME according
+   to the costs in MATRIX, using the direct scrolling method.
+   Update the frame's current_glyphs info to record what was done.
+
+   WINDOW_SIZE is the number of lines being considered for scrolling
+   and UNCHANGED_AT_TOP is the vpos of the first line being considered.
+   These two arguments can specify any contiguous range of lines.
+ 
+   We also shuffle the charstarts vectors for the lines
+   along with the glyphs; but the results are not quite right,
+   since we cannot offset them for changes in amount of text
+   in this line or that line.  Luckily it doesn't matter,
+   since update_frame and update_line will copy in the proper
+   new charstarts vectors from the frame's desired_glyphs.
+
+   In the direct scrolling method, a new scroll window is selected
+   before each insertion or deletion, so that groups of lines can be
+   scrolled directly to their final vertical positions.  This method
+   is described in more detail in calculate_direct_scrolling,
+   where the cost matrix for this approach is constructed. */
+
+static void
+do_direct_scrolling (frame, matrix, window_size, unchanged_at_top)
+     FRAME_PTR frame;
+     struct matrix_elt *matrix;
+     int window_size;
+     int unchanged_at_top;
+{
+  register struct matrix_elt *p;
+  register int i, j;
+  register struct frame_glyphs *current_frame;
+  /* temp_frame->enable[i] means line i has been moved to current_frame.  */
+  register struct frame_glyphs *temp_frame;
+  struct alt_queue { int count, pos, window; } *queue;
+  int offset = unchanged_at_top;
+  int qi = 0;
+  int window = 0;
+  register int tem;
+  int next;
+
+  /* A nonzero value of write_follows indicates that a write has been
+     selected, allowing either an insert or a delete to be selected next.
+     When write_follows is zero, a delete cannot be selected unless j < i,
+     and an insert cannot be selected unless i < j.  This corresponds to
+     a similar restriction (with the ordering reversed) in
+     calculate_direct_scrolling, which is intended to ensure that lines
+     marked as inserted will be blank. */
+  int write_follows = 1;
+
+  queue = (struct alt_queue *) alloca (FRAME_HEIGHT (frame)
+				       * sizeof (struct alt_queue));
+
+  current_frame = FRAME_CURRENT_GLYPHS (frame);
+  temp_frame = FRAME_TEMP_GLYPHS (frame);
+
+  bcopy (current_frame->glyphs, temp_frame->glyphs,
+	 current_frame->height * sizeof (GLYPH *));
+  bcopy (current_frame->charstarts, temp_frame->charstarts,
+	 current_frame->height * sizeof (GLYPH *));
+  bcopy (current_frame->used, temp_frame->used,
+	 current_frame->height * sizeof (int));
+  bcopy (current_frame->highlight, temp_frame->highlight,
+	 current_frame->height * sizeof (char));
+  bzero (temp_frame->enable, temp_frame->height * sizeof (char));
+  bcopy (current_frame->bufp, temp_frame->bufp,
+	 current_frame->height * sizeof (int));
+
+#ifdef HAVE_X_WINDOWS
+  if (FRAME_X_P (frame))
+    {
+      bcopy (current_frame->top_left_x, temp_frame->top_left_x,
+	     current_frame->height * sizeof (short));
+      bcopy (current_frame->top_left_y, temp_frame->top_left_y,
+	     current_frame->height * sizeof (short));
+      bcopy (current_frame->pix_width, temp_frame->pix_width,
+	     current_frame->height * sizeof (short));
+      bcopy (current_frame->pix_height, temp_frame->pix_height,
+	     current_frame->height * sizeof (short));
+      bcopy (current_frame->max_ascent, temp_frame->max_ascent,
+	     current_frame->height * sizeof (short));
+    }
+#endif
+
+  i = j = window_size;
+
+  while (i > 0 || j > 0)
+    {
+      p = matrix + i * (window_size + 1) + j;
+      tem = p->insertcost;
+      if (tem < p->writecost && tem < p->deletecost
+	  && (write_follows || i < j))
+	{
+	  /* Insert should be done at vpos i-1, plus maybe some before.
+	     Setting count to 0 in the queue marks this as an insert. */
+	  write_follows = 0;
+	  queue[qi].window = i + unchanged_at_top;
+	  queue[qi].count = 0;
+	  i -= p->insertcount;
+	  queue[qi++].pos = i + unchanged_at_top;
+	}
+      else if (p->deletecost < p->writecost && (write_follows || i > j))
+	{
+	  /* Delete should be done at vpos j-1, plus maybe some before. */
+	  write_follows = 0;
+	  j -= p->deletecount;
+	}
+      else
+	{
+	  /* One or more lines should be retained. */
+	  write_follows = 1;
+	  tem = p->writecount;
+	  if (i > j)
+	    {
+	      /* Immediately scroll a group of lines downward */
+	      set_terminal_window (i + unchanged_at_top);
+	      window = 1;
+	      ins_del_lines (j - tem + unchanged_at_top, i - j);
+	    }
+	  else if (i < j)
+	    {
+	      /* Queue the upward scrolling of a group of lines */
+	      queue[qi].pos = i - tem + unchanged_at_top;
+	      queue[qi].window = j + unchanged_at_top;
+	      queue[qi++].count = i - j;
+	    }
+
+	  /* Now copy the line-contents strings */
+	  while (tem > 0)
+	    {
+	      i--;
+	      j--;
+	      tem--;
+	      current_frame->glyphs[i + offset]
+		= temp_frame->glyphs[j + offset];
+	      current_frame->charstarts[i + offset]
+		= temp_frame->charstarts[j + offset];
+	      current_frame->used[i + offset]
+		= temp_frame->used[j + offset];
+	      current_frame->highlight[i + offset]
+		= temp_frame->highlight[j + offset];
+
+	      temp_frame->enable[j + offset] = 1;
+	    }
+	}
+    }
+
+  /* Now do all the upward scrolling, and copy the line-contents
+     strings for the blank lines of the recorded inserts. */
+
+  next = unchanged_at_top;
+  for (i = qi - 1; i >= 0; i--)
+    {
+      if (queue[i].count)
+	{
+	  set_terminal_window (queue[i].window);
+	  window = 1;
+	  ins_del_lines (queue[i].pos, queue[i].count);
+	}
+      else
+	{
+	  /* Mark the inserted lines as clear,
+	     and put into them the line-contents strings
+	     that were discarded during the deletions.
+	     Those are the ones for which temp_frame->enable was not set. */
+	  tem = queue[i].pos;
+	  for (j = queue[i].window - 1; j >= tem; j--)
+	    {
+	      current_frame->enable[j] = 0;
+	      while (temp_frame->enable[next])
+		next++;
+	      current_frame->glyphs[j] = temp_frame->glyphs[next];
+	      current_frame->charstarts[j] = temp_frame->charstarts[next++];
+	    }
+	}
+    }
+
+  if (window)
+    set_terminal_window (0);
+}
+
 void
 scrolling_1 (frame, window_size, unchanged_at_top, unchanged_at_bottom,
-	     draw_cost, old_hash, new_hash, free_at_end)
+	     draw_cost, old_draw_cost, old_hash, new_hash, free_at_end)
      FRAME_PTR frame;
      int window_size, unchanged_at_top, unchanged_at_bottom;
      int *draw_cost;
+     int *old_draw_cost;
      int *old_hash;
      int *new_hash;
      int free_at_end;
@@ -375,10 +806,21 @@
   matrix = ((struct matrix_elt *)
 	    alloca ((window_size + 1) * (window_size + 1) * sizeof *matrix));
 
-  calculate_scrolling (frame, matrix, window_size, unchanged_at_bottom,
-		       draw_cost, old_hash, new_hash,
-		       free_at_end);
-  do_scrolling (frame, matrix, window_size, unchanged_at_top);
+  if (scroll_region_ok)
+    {
+      calculate_direct_scrolling (frame, matrix, window_size,
+				  unchanged_at_bottom,
+				  draw_cost, old_draw_cost, 
+				  old_hash, new_hash, free_at_end);
+      do_direct_scrolling (frame, matrix, window_size, unchanged_at_top);
+    }
+  else
+    {
+      calculate_scrolling (frame, matrix, window_size, unchanged_at_bottom,
+			   draw_cost, old_hash, new_hash,
+			   free_at_end);
+      do_scrolling (frame, matrix, window_size, unchanged_at_top);
+    }
 }
 
 /* Return number of lines in common between current and desired frame contents