comparison src/scroll.c @ 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 869e177ca872
children 5c4c0319a6e7
comparison
equal deleted inserted replaced
10261:4fd304db9216 10262:4face60ac721
50 for the cost in insertcost. */ 50 for the cost in insertcost. */
51 unsigned char insertcount; 51 unsigned char insertcount;
52 /* Number of deletes so far in this run of deletes, 52 /* Number of deletes so far in this run of deletes,
53 for the cost in deletecost. */ 53 for the cost in deletecost. */
54 unsigned char deletecount; 54 unsigned char deletecount;
55 /* Number of writes so far since the last insert
56 or delete for the cost in writecost. */
57 unsigned char writecount;
55 }; 58 };
56 59
57 60
58 /* Determine, in matrix[i,j], the cost of updating the first j old lines 61 /* Determine, in matrix[i,j], the cost of updating the first j old
59 into the first i new lines. 62 lines into the first i new lines using the general scrolling method.
60 This involves using insert or delete somewhere if i != j. 63 This involves using insert or delete somewhere if i != j.
61 For each matrix elements, three kinds of costs are recorded: 64 For each matrix elements, three kinds of costs are recorded:
62 the smallest cost that ends with an insert, the smallest 65 the smallest cost that ends with an insert, the smallest
63 cost that ends with a delete, and the smallest cost that 66 cost that ends with a delete, and the smallest cost that
64 ends with neither one. These are kept separate because 67 ends with neither one. These are kept separate because
215 p->deletecost = min (cost, cost1); 218 p->deletecost = min (cost, cost1);
216 p->deletecount = (cost < cost1) ? 1 : p1->deletecount + 1; 219 p->deletecount = (cost < cost1) ? 1 : p1->deletecount + 1;
217 } 220 }
218 } 221 }
219 222
220 /* Perform insert-lines and delete-lines operations on FRAME 223 /* Perform insert-lines and delete-lines operations on FRAME according
221 according to the costs in MATRIX. 224 to the costs in MATRIX, using the general scrolling method.
222 Update the frame's current_glyphs info to record what was done. 225 Update the frame's current_glyphs info to record what was done.
223 226
224 WINDOW_SIZE is the number of lines being considered for scrolling 227 WINDOW_SIZE is the number of lines being considered for scrolling
225 and UNCHANGED_AT_TOP is the vpos of the first line being considered. 228 and UNCHANGED_AT_TOP is the vpos of the first line being considered.
226 These two arguments can specify any contiguous range of lines. 229 These two arguments can specify any contiguous range of lines.
359 362
360 if (window) 363 if (window)
361 set_terminal_window (0); 364 set_terminal_window (0);
362 } 365 }
363 366
367 /* Determine, in matrix[i,j], the cost of updating the first j
368 old lines into the first i new lines using the direct
369 scrolling method. When the old line and the new line have
370 different hash codes, the calculated cost of updating old
371 line j into new line i includes the cost of outputting new
372 line i, and if i != j, the cost of outputting the old line j
373 is also included, as a penalty for moving the line and then
374 erasing it. In addition, the cost of updating a sequence of
375 lines with constant i - j includes the cost of scrolling the
376 old lines into their new positions, unless i == j. Scrolling
377 is achieved by setting the screen window to avoid affecting
378 other lines below, and inserting or deleting lines at the top
379 of the scrolled region. The cost of scrolling a sequence of
380 lines includes the fixed cost of specifying a scroll region,
381 plus a variable cost which can depend upon the number of lines
382 involved and the distance by which they are scrolled, and an
383 extra cost to discourage long scrolls.
384
385 As reflected in the matrix, an insert or delete does not
386 correspond directly to the insertion or deletion which is
387 used in scrolling lines. An insert means that the value of i
388 has increased without a corresponding increase in the value
389 of j. A delete means that the value of j has increased
390 without a corresponding increase in the value of i. A write
391 means that i and j are both increased by the same amount, and
392 that the old lines will be moved to their new positions.
393
394 An insert following a delete is allowed only if i > j.
395 A delete following an insert is allowed only if i < j.
396 These restrictions ensure that the new lines in an insert
397 will always be blank as an effect of the neighboring writes.
398 Thus the calculated cost of an insert is simply the cost of
399 outputting the new line contents. The direct cost of a
400 delete is zero. Inserts and deletes indirectly affect the
401 total cost through their influence on subsequent writes. */
402
403 /* The vectors draw_cost, old_hash, and new_hash have the same
404 meanings here as in calculate_scrolling, and old_draw_cost
405 is the equivalent of draw_cost for the old line contents */
406
407 static void
408 calculate_direct_scrolling (frame, matrix, window_size, lines_below,
409 draw_cost, old_draw_cost, old_hash, new_hash,
410 free_at_end)
411 FRAME_PTR frame;
412 /* matrix is of size window_size + 1 on each side. */
413 struct matrix_elt *matrix;
414 int window_size;
415 int *draw_cost;
416 int *old_draw_cost;
417 int *old_hash;
418 int *new_hash;
419 int free_at_end;
420 {
421 register int i, j;
422 int frame_height = FRAME_HEIGHT (frame);
423 register struct matrix_elt *p, *p1;
424 register int cost, cost1, delta;
425
426 /* first_insert_cost[-I] is the cost of doing the first insert-line
427 at a position I lines above the bottom line in the scroll window. */
428 int *first_insert_cost
429 = &FRAME_INSERT_COST (frame)[frame_height - 1];
430 int *first_delete_cost
431 = &FRAME_DELETE_COST (frame)[frame_height - 1];
432 int *next_insert_cost
433 = &FRAME_INSERTN_COST (frame)[frame_height - 1];
434 int *next_delete_cost
435 = &FRAME_DELETEN_COST (frame)[frame_height - 1];
436
437 int scroll_overhead;
438
439 /* Discourage long scrolls on fast lines.
440 Don't scroll nearly a full frame height unless it saves
441 at least 1/4 second. */
442 int extra_cost = baud_rate / (10 * 4 * FRAME_HEIGHT (frame));
443
444 if (baud_rate <= 0)
445 extra_cost = 1;
446
447 /* Overhead of setting the scroll window, plus the extra cost
448 cost of scrolling by a distance of one. The extra cost is
449 added once for consistency with the cost vectors */
450 scroll_overhead = scroll_region_cost + extra_cost;
451
452 /* initialize the top left corner of the matrix */
453 matrix->writecost = 0;
454 matrix->insertcost = INFINITY;
455 matrix->deletecost = INFINITY;
456 matrix->writecount = 0;
457 matrix->insertcount = 0;
458 matrix->deletecount = 0;
459
460 /* initialize the left edge of the matrix */
461 cost = 0;
462 for (i = 1; i <= window_size; i++)
463 {
464 p = matrix + i * (window_size + 1);
465 cost += draw_cost[i];
466 p->insertcost = cost;
467 p->writecost = INFINITY;
468 p->deletecost = INFINITY;
469 p->insertcount = i;
470 p->writecount = 0;
471 p->deletecount = 0;
472 }
473
474 /* initialize the top edge of the matrix */
475 for (j = 1; j <= window_size; j++)
476 {
477 matrix[j].deletecost = 0;
478 matrix[j].writecost = INFINITY;
479 matrix[j].insertcost = INFINITY;
480 matrix[j].deletecount = j;
481 matrix[j].writecount = 0;
482 matrix[j].insertcount = 0;
483 }
484
485 /* `i' represents the vpos among new frame contents.
486 `j' represents the vpos among the old frame contents. */
487 p = matrix + window_size + 2; /* matrix [1, 1] */
488
489 for (i = 1; i <= window_size; i++, p++)
490 for (j = 1; j <= window_size; j++, p++)
491 {
492 /* p contains the address of matrix [i, j] */
493
494 /* First calculate the cost assuming we do
495 not insert or delete above this line.
496 That is, if we update through line i-1
497 based on old lines through j-1,
498 and then just change old line j to new line i.
499
500 Depending on which choice gives the lower cost,
501 this usually involves either scrolling a single line
502 or extending a sequence of scrolled lines, but
503 when i == j, no scrolling is required. */
504 p1 = p - window_size - 2; /* matrix [i-1, j-1] */
505 cost = p1->insertcost;
506 if (cost > p1->deletecost)
507 cost = p1->deletecost;
508 cost1 = p1->writecost;
509 if (i == j)
510 {
511 if (cost > cost1)
512 {
513 cost = cost1;
514 p->writecount = p1->writecount + 1;
515 }
516 else
517 p->writecount = 1;
518 if (old_hash[j] != new_hash[i])
519 {
520 cost += draw_cost[i];
521 }
522 }
523 else
524 {
525 if (i > j)
526 {
527 delta = i - j;
528
529 /* The cost added here for scrolling the first line by
530 a distance N includes the overhead of setting the
531 scroll window, the cost of inserting N lines at a
532 position N lines above the bottom line of the window,
533 and an extra cost which is proportional to N. */
534 cost += scroll_overhead + first_insert_cost[-delta] +
535 (delta-1) * (next_insert_cost[-delta] + extra_cost);
536
537 /* In the most general case, the insertion overhead and
538 the multiply factor can grow linearly as the distance
539 from the bottom of the window increases. The incremental
540 cost of scrolling an additional line depends upon the
541 rate of change of these two parameters. Each of these
542 growth rates can be determined by a simple difference.
543 To reduce the cumulative effects of rounding error, we
544 vary the position at which the difference is computed. */
545 cost1 += first_insert_cost[-j] - first_insert_cost[1-j] +
546 (delta-1) * (next_insert_cost[-j] - next_insert_cost[1-j]);
547 }
548 else
549 {
550 delta = j - i;
551 cost += scroll_overhead + first_delete_cost[-delta] +
552 (delta-1) * (next_delete_cost[-delta] + extra_cost);
553 cost1 += first_delete_cost[-i] - first_delete_cost[1-i] +
554 (delta-1) * ( next_delete_cost[-i] - next_delete_cost[1-i]);
555 }
556 if (cost1 < cost)
557 {
558 cost = cost1;
559 p->writecount = p1->writecount + 1;
560 }
561 else
562 p->writecount = 1;
563 if (old_hash[j] != new_hash[i])
564 {
565 cost += draw_cost[i] + old_draw_cost[j];
566 }
567 }
568 p->writecost = cost;
569
570 /* Calculate the cost if we do an insert-line
571 before outputting this line.
572 That is, we update through line i-1
573 based on old lines through j,
574 do an insert-line on line i,
575 and then output line i from scratch,
576 leaving old lines starting from j for reuse below. */
577 p1 = p - window_size - 1; /* matrix [i-1, j] */
578 cost = p1->writecost;
579 /* If i > j, an insert is allowed after a delete. */
580 if (i > j && p1->deletecost < cost)
581 cost = p1->deletecost;
582 if (p1->insertcost <= cost)
583 {
584 cost = p1->insertcost;
585 p->insertcount = p1->insertcount + 1;
586 }
587 else
588 p->insertcount = 1;
589 cost += draw_cost[i];
590 p->insertcost = cost;
591
592 /* Calculate the cost if we do a delete line after
593 outputting this line.
594 That is, we update through line i
595 based on old lines through j-1,
596 and throw away old line j. */
597 p1 = p - 1; /* matrix [i, j-1] */
598 cost = p1->writecost;
599 /* If i < j, a delete is allowed after an insert. */
600 if (i < j && p1->insertcost < cost)
601 cost = p1->insertcost;
602 cost1 = p1->deletecost;
603 if (p1->deletecost <= cost)
604 {
605 cost = p1->deletecost;
606 p->deletecount = p1->deletecount + 1;
607 }
608 else
609 p->deletecount = 1;
610 p->deletecost = cost;
611 }
612 }
613
614 /* Perform insert-lines and delete-lines operations on FRAME according
615 to the costs in MATRIX, using the direct scrolling method.
616 Update the frame's current_glyphs info to record what was done.
617
618 WINDOW_SIZE is the number of lines being considered for scrolling
619 and UNCHANGED_AT_TOP is the vpos of the first line being considered.
620 These two arguments can specify any contiguous range of lines.
621
622 We also shuffle the charstarts vectors for the lines
623 along with the glyphs; but the results are not quite right,
624 since we cannot offset them for changes in amount of text
625 in this line or that line. Luckily it doesn't matter,
626 since update_frame and update_line will copy in the proper
627 new charstarts vectors from the frame's desired_glyphs.
628
629 In the direct scrolling method, a new scroll window is selected
630 before each insertion or deletion, so that groups of lines can be
631 scrolled directly to their final vertical positions. This method
632 is described in more detail in calculate_direct_scrolling,
633 where the cost matrix for this approach is constructed. */
634
635 static void
636 do_direct_scrolling (frame, matrix, window_size, unchanged_at_top)
637 FRAME_PTR frame;
638 struct matrix_elt *matrix;
639 int window_size;
640 int unchanged_at_top;
641 {
642 register struct matrix_elt *p;
643 register int i, j;
644 register struct frame_glyphs *current_frame;
645 /* temp_frame->enable[i] means line i has been moved to current_frame. */
646 register struct frame_glyphs *temp_frame;
647 struct alt_queue { int count, pos, window; } *queue;
648 int offset = unchanged_at_top;
649 int qi = 0;
650 int window = 0;
651 register int tem;
652 int next;
653
654 /* A nonzero value of write_follows indicates that a write has been
655 selected, allowing either an insert or a delete to be selected next.
656 When write_follows is zero, a delete cannot be selected unless j < i,
657 and an insert cannot be selected unless i < j. This corresponds to
658 a similar restriction (with the ordering reversed) in
659 calculate_direct_scrolling, which is intended to ensure that lines
660 marked as inserted will be blank. */
661 int write_follows = 1;
662
663 queue = (struct alt_queue *) alloca (FRAME_HEIGHT (frame)
664 * sizeof (struct alt_queue));
665
666 current_frame = FRAME_CURRENT_GLYPHS (frame);
667 temp_frame = FRAME_TEMP_GLYPHS (frame);
668
669 bcopy (current_frame->glyphs, temp_frame->glyphs,
670 current_frame->height * sizeof (GLYPH *));
671 bcopy (current_frame->charstarts, temp_frame->charstarts,
672 current_frame->height * sizeof (GLYPH *));
673 bcopy (current_frame->used, temp_frame->used,
674 current_frame->height * sizeof (int));
675 bcopy (current_frame->highlight, temp_frame->highlight,
676 current_frame->height * sizeof (char));
677 bzero (temp_frame->enable, temp_frame->height * sizeof (char));
678 bcopy (current_frame->bufp, temp_frame->bufp,
679 current_frame->height * sizeof (int));
680
681 #ifdef HAVE_X_WINDOWS
682 if (FRAME_X_P (frame))
683 {
684 bcopy (current_frame->top_left_x, temp_frame->top_left_x,
685 current_frame->height * sizeof (short));
686 bcopy (current_frame->top_left_y, temp_frame->top_left_y,
687 current_frame->height * sizeof (short));
688 bcopy (current_frame->pix_width, temp_frame->pix_width,
689 current_frame->height * sizeof (short));
690 bcopy (current_frame->pix_height, temp_frame->pix_height,
691 current_frame->height * sizeof (short));
692 bcopy (current_frame->max_ascent, temp_frame->max_ascent,
693 current_frame->height * sizeof (short));
694 }
695 #endif
696
697 i = j = window_size;
698
699 while (i > 0 || j > 0)
700 {
701 p = matrix + i * (window_size + 1) + j;
702 tem = p->insertcost;
703 if (tem < p->writecost && tem < p->deletecost
704 && (write_follows || i < j))
705 {
706 /* Insert should be done at vpos i-1, plus maybe some before.
707 Setting count to 0 in the queue marks this as an insert. */
708 write_follows = 0;
709 queue[qi].window = i + unchanged_at_top;
710 queue[qi].count = 0;
711 i -= p->insertcount;
712 queue[qi++].pos = i + unchanged_at_top;
713 }
714 else if (p->deletecost < p->writecost && (write_follows || i > j))
715 {
716 /* Delete should be done at vpos j-1, plus maybe some before. */
717 write_follows = 0;
718 j -= p->deletecount;
719 }
720 else
721 {
722 /* One or more lines should be retained. */
723 write_follows = 1;
724 tem = p->writecount;
725 if (i > j)
726 {
727 /* Immediately scroll a group of lines downward */
728 set_terminal_window (i + unchanged_at_top);
729 window = 1;
730 ins_del_lines (j - tem + unchanged_at_top, i - j);
731 }
732 else if (i < j)
733 {
734 /* Queue the upward scrolling of a group of lines */
735 queue[qi].pos = i - tem + unchanged_at_top;
736 queue[qi].window = j + unchanged_at_top;
737 queue[qi++].count = i - j;
738 }
739
740 /* Now copy the line-contents strings */
741 while (tem > 0)
742 {
743 i--;
744 j--;
745 tem--;
746 current_frame->glyphs[i + offset]
747 = temp_frame->glyphs[j + offset];
748 current_frame->charstarts[i + offset]
749 = temp_frame->charstarts[j + offset];
750 current_frame->used[i + offset]
751 = temp_frame->used[j + offset];
752 current_frame->highlight[i + offset]
753 = temp_frame->highlight[j + offset];
754
755 temp_frame->enable[j + offset] = 1;
756 }
757 }
758 }
759
760 /* Now do all the upward scrolling, and copy the line-contents
761 strings for the blank lines of the recorded inserts. */
762
763 next = unchanged_at_top;
764 for (i = qi - 1; i >= 0; i--)
765 {
766 if (queue[i].count)
767 {
768 set_terminal_window (queue[i].window);
769 window = 1;
770 ins_del_lines (queue[i].pos, queue[i].count);
771 }
772 else
773 {
774 /* Mark the inserted lines as clear,
775 and put into them the line-contents strings
776 that were discarded during the deletions.
777 Those are the ones for which temp_frame->enable was not set. */
778 tem = queue[i].pos;
779 for (j = queue[i].window - 1; j >= tem; j--)
780 {
781 current_frame->enable[j] = 0;
782 while (temp_frame->enable[next])
783 next++;
784 current_frame->glyphs[j] = temp_frame->glyphs[next];
785 current_frame->charstarts[j] = temp_frame->charstarts[next++];
786 }
787 }
788 }
789
790 if (window)
791 set_terminal_window (0);
792 }
793
364 void 794 void
365 scrolling_1 (frame, window_size, unchanged_at_top, unchanged_at_bottom, 795 scrolling_1 (frame, window_size, unchanged_at_top, unchanged_at_bottom,
366 draw_cost, old_hash, new_hash, free_at_end) 796 draw_cost, old_draw_cost, old_hash, new_hash, free_at_end)
367 FRAME_PTR frame; 797 FRAME_PTR frame;
368 int window_size, unchanged_at_top, unchanged_at_bottom; 798 int window_size, unchanged_at_top, unchanged_at_bottom;
369 int *draw_cost; 799 int *draw_cost;
800 int *old_draw_cost;
370 int *old_hash; 801 int *old_hash;
371 int *new_hash; 802 int *new_hash;
372 int free_at_end; 803 int free_at_end;
373 { 804 {
374 struct matrix_elt *matrix; 805 struct matrix_elt *matrix;
375 matrix = ((struct matrix_elt *) 806 matrix = ((struct matrix_elt *)
376 alloca ((window_size + 1) * (window_size + 1) * sizeof *matrix)); 807 alloca ((window_size + 1) * (window_size + 1) * sizeof *matrix));
377 808
378 calculate_scrolling (frame, matrix, window_size, unchanged_at_bottom, 809 if (scroll_region_ok)
379 draw_cost, old_hash, new_hash, 810 {
380 free_at_end); 811 calculate_direct_scrolling (frame, matrix, window_size,
381 do_scrolling (frame, matrix, window_size, unchanged_at_top); 812 unchanged_at_bottom,
813 draw_cost, old_draw_cost,
814 old_hash, new_hash, free_at_end);
815 do_direct_scrolling (frame, matrix, window_size, unchanged_at_top);
816 }
817 else
818 {
819 calculate_scrolling (frame, matrix, window_size, unchanged_at_bottom,
820 draw_cost, old_hash, new_hash,
821 free_at_end);
822 do_scrolling (frame, matrix, window_size, unchanged_at_top);
823 }
382 } 824 }
383 825
384 /* Return number of lines in common between current and desired frame contents 826 /* Return number of lines in common between current and desired frame contents
385 described to us only as vectors of hash codes OLDHASH and NEWHASH. 827 described to us only as vectors of hash codes OLDHASH and NEWHASH.
386 Consider only vpos range START to END (not including END). 828 Consider only vpos range START to END (not including END).