1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright (c) 1995, by Sun Microsystems, Inc.
  24  * All rights reserved.
  25  */
  26 
  27 #pragma ident   "%Z%%M% %I%     %E% SMI"
  28 
  29 /*
  30  * doupdate.c
  31  *
  32  * XCurses Library
  33  *
  34  * Copyright 1990, 1995 by Mortice Kern Systems Inc.  All rights reserved.
  35  *
  36  */
  37 
  38 #ifdef M_RCSID
  39 #ifndef lint
  40 static char const rcsID[] = "$Header: /rd/src/libc/xcurses/rcs/doupdate.c 1.9 1995/07/26 17:45:06 ant Exp $";
  41 #endif
  42 #endif
  43 
  44 #include <private.h>
  45 #include <string.h>
  46 #include <setjmp.h>
  47 #include <signal.h>
  48 
  49 #undef SIGTSTP
  50 
  51 /*
  52  * Disable typeahead trapping because it slow down updated dramatically
  53  * on MPE/iX.
  54  */
  55 #ifdef MPE_STUB
  56 #undef M_CURSES_TYPEAHEAD
  57 #endif
  58 
  59 /*
  60  * This value is the ideal length for the cursor addressing sequence 
  61  * being four bytes long, ie. "<escape><cursor addressing code><row><col>".
  62  * eg. VT52 - "\EYrc" or ADM3A - "\E=rc"
  63  */
  64 #define JUMP_SIZE       4
  65 
  66 /*
  67  * This value is the ideal length for the clear-to-eol sequence
  68  * being two bytes long, ie "<escape><clear eol code>".
  69  */
  70 #define CEOL_SIZE       2
  71 
  72 #define GOTO(r,c)       (__m_mvcur(curscr->_cury, curscr->_curx,r,c,__m_outc),\
  73                         curscr->_cury = r, curscr->_curx = c)
  74 
  75 typedef struct cost_op {
  76         short cost;
  77         short op;
  78 } lcost;
  79 
  80 typedef void (*t_action)(int, int);
  81 
  82 static jmp_buf breakout;
  83 
  84 #define LC(i,j)         (lc[(i) * (LINES + 1) + (j)])
  85 
  86 static lcost *lc = (lcost *) 0;
  87 static unsigned long *nhash = (unsigned long *) 0;
  88 static t_action *del = (t_action *) 0;
  89 static t_action *ins_rep = (t_action *) 0;
  90 
  91 static WINDOW *newscr;
  92 
  93 STATIC void erase_bottom(int);
  94 STATIC void clear_bottom(int);
  95 STATIC void complex(void);
  96 STATIC int cost(int, int);
  97 STATIC void lines_delete(int, int);
  98 STATIC void lines_insert(int, int);
  99 STATIC void lines_replace(int, int);
 100 STATIC void script(int, int);
 101 STATIC int scroll_dn(int);
 102 STATIC int scroll_up(int);
 103 STATIC void simple(void);
 104 STATIC void text_replace(int);
 105 STATIC void block_over(int, int, int);
 106 
 107 /*f
 108  * Wrapper that streams Curses output.
 109  * 
 110  * All escape sequences going to the screen come through here.
 111  * All ordinary characters go to the screen via the putc in doupdate.c
 112  */
 113 int
 114 __m_outc(ch)
 115 int ch;
 116 {
 117         return putc(ch, __m_screen->_of);
 118 }
 119 
 120 /*
 121  * Allocate or grow doupdate() structures.
 122  */
 123 int
 124 __m_doupdate_init() 
 125 {
 126         void *new;
 127         static short nlines = 0;
 128 
 129         if (lines <= 0)
 130                 return -1;
 131 
 132         if (lines <= nlines)
 133                 return 0;
 134 
 135         new = m_malloc((lines+1) * (lines+1) * sizeof *lc);
 136         if (new == (void *) 0)
 137                 return -1;
 138         if (lc != (lcost *) 0)
 139                 free(lc);
 140         lc = (lcost *) new;
 141 
 142         new = m_malloc((lines + lines) * sizeof *del);
 143         if (new == (void *) 0)
 144                 return -1;
 145         if (del != (t_action *) 0)
 146                 free(del);
 147         del = (t_action *) new;
 148         ins_rep = del + lines;
 149 
 150         new = m_malloc(lines * sizeof *nhash);
 151         if (new == (void *) 0)
 152                 return -1;
 153         if (nhash != (unsigned long *) 0)
 154                 free(nhash);
 155         nhash = (unsigned long *) new;
 156 
 157         nlines = lines;
 158 
 159         return 0;
 160 }
 161 
 162 STATIC void
 163 erase_bottom(y)
 164 int y;
 165 {
 166         int i;
 167 
 168         for (i = y; i < LINES; ++i) {
 169                 (void) __m_cc_erase(curscr, i, 0, i, curscr->_maxx-1);
 170                 __m_cc_hash(curscr, __m_screen->_hash, i);
 171         }
 172 }
 173 
 174 /*f
 175  *  Clear from the start of the current row to bottom of screen.
 176  */
 177 STATIC void
 178 clear_bottom(y)
 179 int y;
 180 {
 181         erase_bottom(y);
 182 
 183         /* Restore default color pair before doing area clears. */
 184         if (back_color_erase)
 185                 (void) vid_puts(WA_NORMAL, 0, (void *) 0, __m_outc);
 186 
 187         if (y == 0 && clear_screen != (char *) 0) {
 188                 (void) tputs(clear_screen, 1, __m_outc);
 189         } else {
 190                 (void) __m_mvcur(-1, -1, y, 0, __m_outc);
 191                 if (clr_eos != (char *) 0) {
 192                         (void) tputs(clr_eos, 1, __m_outc);
 193                 } else if (clr_eol != (char *) 0) {
 194                         for (;;) {
 195                                 (void) tputs(clr_eol, 1, __m_outc);
 196                                 if (LINES <= y)
 197                                         break;
 198                                 (void) __m_mvcur(y, 0, y+1, 0, __m_outc);
 199                                 ++y;
 200                         }
 201                 }
 202         }
 203 
 204         curscr->_cury = y;
 205         curscr->_curx = 0;
 206 }
 207 
 208 /*f
 209  * Replace a line of text.  
 210  *
 211  * The principal scheme is to overwrite the region of a line between
 212  * the first and last differing characters.  A clear-eol is used to
 213  * optimise an update region that consist largely of blanks.  This can
 214  * happen fairly often in the case of scrolled lines or full redraws.
 215  *
 216  * Miller's line redraw algorithm, used in the 'S' editor [Mil87], 
 217  * should be re-investigated to see if it is simple and fast enough for 
 218  * our needs, and if it can be adapted to handle the ceol_standout_glitch 
 219  * (HP 2392A terminals) and multibyte character sequences.
 220  *
 221  * Very early versions of this code applied a Gosling algorithm column
 222  * wise in addition to the row-wise used in complex().  It was removed
 223  * in favour of both computation and transmission speed.  The assumption
 224  * being that overwrites of a line region occured far more frequently
 225  * than the need to insert/delete several isolated characters. 
 226  *
 227  * References:
 228  * [Mil87]      W. Miller, A Software Tools Sampler, Prentice-Hall, 1987
 229  */
 230 STATIC void
 231 text_replace(row)
 232 int row;
 233 {
 234         short npair;
 235         attr_t cookie, nattr; 
 236         cchar_t *optr, *nptr;
 237         int col, last, tail, jump, count;
 238         
 239 #ifdef M_CURSES_TYPEAHEAD
 240         /* Before replacing a line of text, check for type-ahead. */
 241         if (__m_screen->_flags & S_ISATTY) {
 242                 unsigned char cc;
 243 
 244                 if (read(__m_screen->_kfd, &cc, sizeof cc) == sizeof cc) {
 245                         (void) ungetch(cc);
 246                         longjmp(breakout, 1);
 247                 }
 248         }
 249 #endif /* M_CURSES_TYPEAHEAD */
 250 
 251         col = newscr->_first[row];
 252         if (col < 0)
 253                 col = 0;
 254 
 255         last = newscr->_last[row];
 256         if (COLS < last)
 257                 last = COLS;
 258 
 259         if (clr_eol != (char *) 0) {
 260                 /* Find start of blank tail region. */
 261                 nptr = &newscr->_line[row][COLS];
 262                 for (tail = COLS; 0 < tail; --tail) {
 263                         if (!__m_cc_compare(--nptr, &newscr->_bg, 1))
 264                                 break;
 265                 }
 266 
 267                 /* Only consider clear-to-end-of-line optimization if the
 268                  * blank tail falls within the end of the dirty region by
 269                  * more than ideal length of clear-to-end-of-line sequence.
 270                  * Else disable the check by forcing tail to be at the
 271                  * end-of-line.
 272                  */
 273                 if (last < tail + CEOL_SIZE)
 274                         tail = COLS;
 275         }
 276 
 277         optr = &curscr->_line[row][col];
 278         nptr = &newscr->_line[row][col];
 279 
 280         for (jump = -1; col < last; ) {
 281                 /* Skip common regions. */
 282                 for (count = 0; __m_cc_compare(optr, nptr, 1); ++count) {
 283                         /* Advance before possible goto. */
 284                         ++optr;
 285                         ++nptr;
 286 
 287                         if (last <= ++col)
 288                                 goto done;
 289                 }
 290 
 291                 /* Move the cursor by redrawing characters or using
 292                  * cursor motion commands.  The first time that we
 293                  * address this row, jump equals -1, so that the cursor
 294                  * will be forced to the correct screen line.  Once
 295                  * there, we should be able to track the cursor motion
 296                  * along the line and jump only when the cost of redrawing
 297                  * to column N is more expensive than a jump to column N.
 298                  */
 299                 if (jump < count) {
 300                         /* First time addressing this row or cost of
 301                          * jumping cheaper than redrawing.
 302                          */
 303                         jump = JUMP_SIZE;
 304                         GOTO(row, col);
 305                         count = 0;
 306 
 307                         /* If attributes at start of field are different
 308                          * force an attribute cookie to be dropped.
 309                          */
 310                         if (ceol_standout_glitch 
 311                         && (optr->_at != nptr->_at || optr->_co != nptr->_co))
 312                                 ATTR_STATE |= WA_COOKIE;
 313                 } else {
 314                         /* Redraw to move short distance. */
 315                         optr -= count;
 316                         nptr -= count;
 317                         col -= count;
 318                 }
 319 
 320                 /* Write difference region. */
 321                 while (col < last 
 322                 && (!__m_cc_compare(optr, nptr, 1) || 0 < count--)) {
 323 write_loop:
 324                         /* Check for clear-to-end-of-line optimization. */
 325                         if (clr_eol != (char *) 0 && tail <= col) {
 326                                 /* For HP terminals, only clear-to-end-of-line
 327                                  * once the attributes have been turned off.
 328                                  * Other terminals, we can proceed normally.
 329                                  */
 330                                 if (!ceol_standout_glitch
 331                                 || ATTR_STATE == WA_NORMAL) {
 332                                         curscr->_curx = col;
 333                                         goto done;
 334                                 }
 335                         }
 336 
 337                         ++col;
 338 
 339                         /* Make sure we don't scroll the screen by writing
 340                          * to the bottom right corner.  
 341                          */
 342                         if (COLS <= col && LINES-1 <= row
 343                         && auto_right_margin && !eat_newline_glitch) {
 344                                 /*** TODO
 345                                  *** Insert character/auto_right_margin
 346                                  *** hacks for writting into the last
 347                                  *** column of the last line so as not
 348                                  *** to scroll.
 349                                  ***/ 
 350                                 curscr->_curx = col;
 351                                 goto done;
 352                         }
 353 
 354                         /* Remember any existing attribute cookie. */
 355                         cookie = optr->_at & WA_COOKIE;
 356 
 357                         nattr = nptr->_at;
 358                         npair = nptr->_co;
 359 
 360                         /* Change attribute state.  On HP terminals we also
 361                          * have to check for attribute cookies that may need
 362                          * to be changed.
 363                          */
 364                         if (ATTR_STATE != nattr 
 365                         || optr->_at != nattr || optr->_co != npair) {
 366                                 (void) vid_puts(
 367                                         nattr, npair, (void *) 0, __m_outc
 368                                 );
 369 
 370                                 /* Remember new or existing cookie. */
 371                                 cookie = WA_COOKIE;
 372                         }
 373 
 374                         /* Don't display internal characters. */
 375                         if (nptr->_f) 
 376                                 (void) __m_cc_write(nptr);
 377 
 378                         /* Update copy of screen image. */
 379                         *optr++ = *nptr++;
 380                         optr->_at |= cookie;
 381                 }
 382 
 383                 curscr->_curx = col;
 384 
 385                 /* Check the attributes at the end of the field with
 386                  * those of start of the next common region.  If they
 387                  * differ, force another iteration of the write-loop
 388                  * that will change the attribute state.
 389                  */ 
 390                 if (ceol_standout_glitch && col < COLS 
 391                 && ATTR_STATE != (optr->_at & ~WA_COOKIE))
 392                         goto write_loop;
 393         }
 394 done:
 395         /* Before leaving this line, check if we have to turn off 
 396          * attributes and record a cookie.
 397          */
 398         if (!move_standout_mode && ATTR_STATE != WA_NORMAL) {
 399                 /* ceol_standout_glitch, which affects HP terminals,
 400                  * drops hidden cookies on the screen where ever the
 401                  * cursor is, so disabling attributes before a cursor
 402                  * motion operation could disturb existing highlights.
 403                  */
 404                 if (ceol_standout_glitch)
 405                         /* Attributes on an HP terminal do not cross lines. */
 406                         ATTR_STATE = A_NORMAL;
 407                 else
 408                         (void) vid_puts(WA_NORMAL, 0, (void *) 0, __m_outc);
 409         }
 410 
 411         /* Re-check for clear to end-of-line optimization. */
 412         if (clr_eol != (char *) 0 && tail <= col && col < last) {
 413                 /* Is the tail of the current screen image non-blank? */
 414                 for (tail = col; tail < COLS; ++tail, ++optr)
 415                         if (!__m_cc_compare(optr, &newscr->_bg, 1))
 416                                 break;
 417 
 418                 /* If tail didn't reach the right margin of
 419                  * the current screen image, then we will
 420                  * make it look like the new image with a
 421                  * clear to end-of-line.
 422                  */
 423                 if (tail < COLS) {
 424                         /* Restore default color pair before area clear. */
 425                         if (back_color_erase)
 426                                 (void) vid_puts(
 427                                         WA_NORMAL, 0, (void *) 0, __m_outc
 428                                 );
 429 
 430                         (void) tputs(clr_eol, 1, __m_outc);
 431                         __m_cc_erase(curscr, row, tail, row, COLS-1);
 432                 }
 433         } 
 434 
 435         /* Line wrapping checks. */
 436         if (COLS <= curscr->_curx) {
 437                 --curscr->_curx;
 438                 if (auto_right_margin && row < LINES-1) {
 439                         if (eat_newline_glitch) {
 440                                 __m_outc('\r');
 441                                 __m_outc('\n');
 442                         }
 443                         ++curscr->_cury;
 444                         curscr->_curx = 0;
 445                 }
 446         } 
 447 }
 448 
 449 /*f
 450  * Replace a block of lines.
 451  * Only ever used for complex().
 452  */
 453 STATIC void 
 454 lines_replace(from, to_1)
 455 int from, to_1;
 456 {
 457         for (; from < to_1; ++from)
 458                 text_replace(from);
 459 }
 460 
 461 /*f
 462  * Delete a block of lines.
 463  * Only ever used for complex().
 464  */
 465 STATIC void 
 466 lines_delete(from, to_1)
 467 int from, to_1;
 468 {
 469         int count = to_1 - from;
 470 
 471         if (LINES <= to_1) {
 472                 clear_bottom(from);
 473         } else {
 474                 GOTO(from, 0);
 475                 (void) winsdelln(curscr, -count);
 476 
 477                 if (parm_delete_line != (char *) 0) {
 478                         /* Assume that the sequence to delete more than one 
 479                          * line is faster than repeated single delete_lines. 
 480                          */
 481                         (void) tputs(
 482                                 tparm(
 483                                         parm_delete_line, (long) count,
 484                                         0, 0, 0, 0, 0, 0, 0, 0
 485                                 ), count, __m_outc
 486                         );
 487                 } else if (delete_line != (char *) 0) {
 488                         while (from++ < to_1)
 489                                 (void) tputs(delete_line, 1, __m_outc);
 490                 } else  {
 491                         /* Error -- what to do. */
 492                         return;
 493                 }
 494         }
 495 }
 496 
 497 /*f
 498  * Insert a block of lines.
 499  * Only ever used for complex().
 500  *
 501  * We must assume that insert_line and parm_insert_line reset the 
 502  * cursor column to zero.  Therefore it is text_replace() responsiblity
 503  * to move the cursor to the correct column to begin the update.
 504  */
 505 STATIC void 
 506 lines_insert(from, to_1)
 507 int from, to_1;
 508 {
 509         int row, count = to_1 - from;
 510 
 511         /* Position the cursor and insert a block of lines into the screen
 512          * image now, insert lines into the physical screen, then draw the
 513          * new screen lines.  
 514          */ 
 515         GOTO(from, 0);
 516         (void) winsdelln(curscr, count);
 517 
 518         if (parm_insert_line != (char *) 0) {
 519                 /* Assume that the sequence to insert more than one line is
 520                  * faster than repeated single insert_lines. 
 521                  */
 522                 (void) tputs(
 523                         tparm(
 524                                 parm_insert_line, (long) count,
 525                                 0, 0, 0, 0, 0, 0, 0, 0
 526                         ), count, __m_outc
 527                 );
 528         } else if (insert_line != (char *) 0) {
 529                 /* For the single line insert we use to iterate moving
 530                  * the cursor, inserting, and then drawing a line.  That
 531                  * would appear to be slow but visually appealing.  However,
 532                  * people on slow terminals want speed and those on fast
 533                  * terminal won't see it.
 534                  */
 535                 for (row = from; row < to_1; ++row)
 536                         (void) tputs(insert_line, 1, __m_outc);
 537         } else {
 538                 /* Error -- what to do. */
 539                 return;
 540         }
 541 
 542         for (row = from; row < to_1; ++row)
 543                 text_replace(row);
 544 }
 545 
 546 STATIC int
 547 scroll_up(n)
 548 int n;
 549 {
 550         int count = n;
 551         int start, finish, to, row;
 552 
 553         if (scroll_forward != (char *) 0) {
 554                 GOTO(LINES-1, 0);
 555                 while (0 < n--)
 556                         (void) tputs(scroll_forward, 1, __m_outc);
 557         } else if (parm_delete_line != (char *) 0 && 1 < n) {
 558                 GOTO(0, 0);
 559                 (void) tputs(
 560                         tparm(
 561                                 parm_delete_line, (long) n, 
 562                                 0, 0, 0, 0, 0, 0, 0, 0
 563                         ), n, __m_outc
 564                 );
 565         } else if (delete_line != (char *) 0) {
 566                 GOTO(0, 0);
 567                 while (0 < n--)
 568                         (void) tputs(delete_line, 1, __m_outc);
 569         } else {
 570                 return 0;
 571         }
 572 
 573         /* Scroll recorded image. */
 574         start = 0;
 575         finish = count-1;
 576         to = lines;
 577 
 578         (void) __m_cc_erase(curscr, start, 0, finish, curscr->_maxx-1);
 579         (void) __m_ptr_move(
 580                 (void **) curscr->_line, curscr->_maxy, start, finish, to
 581         ); 
 582 
 583         simple();
 584 
 585         return 1;
 586 }
 587 
 588 STATIC int 
 589 scroll_dn(n)
 590 int n;
 591 {
 592         int count = n;
 593         int start, finish, to, row;
 594 
 595         if (LINES < n)
 596                 return 0;
 597 
 598         if (scroll_reverse != (char *) 0) {
 599                 GOTO(0, 0);
 600                 while (0 < n--)
 601                         (void) tputs(scroll_reverse, 1, __m_outc);
 602         } else if (parm_insert_line != (char *) 0 && 1 < n) {
 603                 GOTO(0, 0);
 604                 (void) tputs(
 605                         tparm(
 606                                 parm_insert_line, (long) n, 
 607                                 0, 0, 0, 0, 0, 0, 0, 0
 608                         ), n, __m_outc
 609                 );
 610         } else if (insert_line != (char *) 0) {
 611                 GOTO(0, 0);
 612                 while (0 < n--)
 613                         (void) tputs(insert_line, 1, __m_outc);
 614         } else {
 615                 return 0;
 616         }
 617 
 618         /* Scroll recorded image. */
 619         start = lines - count;
 620         finish = lines - 1;
 621         to = 0;
 622 
 623         (void) __m_cc_erase(curscr, start, 0, finish, curscr->_maxx-1);
 624         (void) __m_ptr_move(
 625                 (void **) curscr->_line, curscr->_maxy, start, finish, to
 626         ); 
 627 
 628         simple();
 629 
 630         return 1;
 631 }
 632 
 633 #ifdef NEVER
 634 STATIC int
 635 is_same_line(old, new, count)
 636 cchar_t *old, *new;
 637 int count;
 638 {
 639         while (0 < count--)
 640                 if (!__m_cc_compare(old, new, 1))
 641                         return 0;
 642 
 643         return 1;
 644 }
 645 #endif /* NEVER */
 646 
 647 /*f
 648  * Dynamic programming algorithm for the string edit problem.
 649  *
 650  * This is a modified Gosling cost algorithm that takes into account
 651  * null/move operations. 
 652  *
 653  * Costs for move, delete, replace, and insert are 0, 1, 2, and 3
 654  * repectively. 
 655  */
 656 STATIC int
 657 cost(fr, lr)
 658 int fr, lr;
 659 {
 660         register lcost *lcp;
 661         register int or, nr, cc;
 662         register unsigned long *ohash = __m_screen->_hash;
 663         cchar_t **oline = curscr->_line;
 664         cchar_t **nline = newscr->_line;
 665         int linesz = COLS * sizeof **oline;
 666 
 667         /* Prepare initial row and column of cost matrix. 
 668          *
 669          *      0 3 6 9 ...
 670          *      1
 671          *      2
 672          *      3
 673          *      :
 674          */
 675         LC(fr,fr).cost = 0;
 676         for (cc = 1, ++lr, nr = fr+1; nr <= lr; ++nr, ++cc) {
 677                 /* Top row is 3, 6, 9, ... */
 678                 LC(fr,nr).cost = cc * 3;
 679                 LC(fr,nr).op = 'i';
 680 
 681                 /* Left column is 1, 2, 3, ... */
 682                 LC(nr,fr).cost = cc; 
 683                 LC(nr,fr).op = 'd'; 
 684         }
 685 
 686         for (--lr, or = fr; or <= lr; ++or) {
 687                 for (nr = fr; nr <= lr; ++nr) {
 688                         lcp = &LC(or+1,nr+1);
 689 
 690                         /* Assume move op. */
 691                         lcp->cost = LC(or,nr).cost; 
 692                         lcp->op = 'm';
 693 
 694                         if (ohash[or] != nhash[nr]
 695 #ifdef NEVER
 696 /* Should no longer require this code.  Using the POSIX 32-bit CRC to
 697  * generate a hash value should be sufficient now, since text_replace() 
 698  * will compare the contents of a line and output only the dirty regions.
 699  */
 700                         || !is_same_line(oline[or], nline[nr], linesz)
 701 #endif
 702                         ) {
 703                                 /* Lines are different, assume replace op. */
 704                                 lcp->cost += 2;
 705                                 lcp->op = 'r';
 706                         }
 707 
 708                         /* Compare insert op. */
 709                         if ((cc = LC(or+1,nr).cost + 3) < lcp->cost) {
 710                                 lcp->cost = cc;
 711                                 lcp->op = 'i';
 712                         }
 713 
 714                         /* Compare delete op. */
 715                         if ((cc = LC(or,nr+1).cost + 1) < lcp->cost) {
 716                                 lcp->cost = cc;
 717                                 lcp->op = 'd';
 718                         }
 719                 }
 720         }
 721 
 722         return LC(lr+1,lr+1).cost;
 723 }
 724 
 725 /*f
 726  * Build edit script.
 727  *
 728  * Normally this would be a recursve routine doing the deletes, inserts,
 729  * and replaces on individual lines. Instead we build the script so that
 730  * we can later do the operations on a block basis.  For terminals with
 731  * parm_delete or parm_insert strings this will be better in terms of the
 732  * number of characters sent to delete and insert a block of lines.
 733  *
 734  * Also we can optimize the script so that tail inserts become replaces.
 735  * This saves unnecessary inserts operations when the tail can just be
 736  * overwritten.
 737  */
 738 STATIC void
 739 script(fr, lr)
 740 int fr, lr;
 741 {
 742         int i, j;
 743         cchar_t *cp;
 744 
 745         i = j = lr + 1; 
 746 
 747         memset(del, 0, sizeof *del * LINES);
 748         memset(ins_rep, 0, sizeof *ins_rep * LINES);
 749 
 750         do {
 751                 /* We don't have to bounds check i or j becuase row fr and 
 752                  * column fr of lc have been preset in order to guarantee the 
 753                  * correct motion.
 754                  */
 755                 switch (LC(i,j).op) {
 756                 case 'i':
 757                         --j;
 758                         ins_rep[j] = lines_insert;
 759                         break;
 760                 case 'd':
 761                         --i;
 762                         del[i] = lines_delete;
 763                         break;
 764                 case 'm':
 765                         --i;
 766                         --j;
 767                         break;
 768                 case 'r':
 769                         --i;
 770                         --j;
 771                         ins_rep[j] = lines_replace;
 772                         break;
 773                 }
 774         } while (fr < i || fr < j);
 775 
 776         /* Optimize Tail Inserts */
 777         for (i = LINES-1; 0 <= i && ins_rep[i] == lines_insert; --i) {
 778                 /* Make each character in the screen line image invalid. */
 779                 for (cp = curscr->_line[i], j = 0; j < COLS; ++j, ++cp)
 780                         cp->_n = -1;
 781                 ins_rep[i] = lines_replace;
 782         }
 783 }
 784  
 785 /*f
 786  * Complex update algorithm using insert/delete line operations.
 787  *
 788  * References:
 789  * [MyM86]      E.W. Myers & W. Miller, Row Replacement Algorithms for 
 790  *              Screen Editors, TR 86-19, Dept. Computer Science, U. of Arizona
 791  * [MyM87]      E.W. Myers & W. Miller, A Simple Row Replacement Method,
 792  *              TR 86-28, Dept. Computer Science, U. of Arizona
 793  * [Mil87]      W. Miller, A Software Tools Sampler, Prentice-Hall, 1987
 794  * [Gos81]      James Gosling, A redisplay algorithm, Proceedings of the 
 795  *              ACM Symposium on Text Manipulation, SIGPLAN Notices, 
 796  *              16(6) June 1981, pg 123-129
 797  *
 798  * All the above were reviewed and experimented with.  Due to the nature of
 799  * Curses' having to handling overlapping WINDOWs, the only suitable
 800  * algorithum is [Gos81].  The others are better suited to editor type
 801  * applications that have one window being the entire terminal screen.
 802  *
 803  */
 804 STATIC void
 805 complex()
 806 {
 807         int fr = -1;
 808         int i, j, lr;
 809         t_action func;
 810 
 811         /* Find block of lines to change */
 812         for (i = 0; i < LINES; ++i) {
 813                 if (newscr->_first[i] < newscr->_last[i]) {
 814                         /* Compute new hash. */
 815                         __m_cc_hash(newscr, nhash, i);
 816                         if (fr == -1)
 817                                 fr = i;
 818                         lr = i;
 819                 } else {
 820                         /* Line not dirty so hash same as before. */
 821                         nhash[i] = __m_screen->_hash[i];
 822                 }
 823         }
 824 
 825         if (fr != -1) {
 826                 /* Gosling */
 827                 cost(fr, lr);
 828                 script(fr, lr);
 829 
 830                 /* Do deletes first in reverse order. */
 831                 for (j = lr; fr <= j; --j) {
 832                         if (del[j] != (t_action) 0) {
 833                                 for (i = j-1; fr <= i; --i)
 834                                         if (del[i] == (t_action) 0)
 835                                                 break;
 836 
 837                                 lines_delete(i+1, j+1);
 838                                 j = i;
 839                         }
 840                 }
 841 
 842                 /* Do insert/replace in forward order. */
 843                 for (i = fr; i <= lr; ++i) {
 844                         if ((func = ins_rep[i]) != (t_action) 0) {
 845                                 /* Find size of block */
 846                                 for (j = i; j <= lr && ins_rep[j] == func; ++j)
 847                                         ;
 848                                 (*func)(i, j);
 849                                 i = j-1;
 850                         }
 851                 }
 852 record:
 853                 /* _line[], which contains pointers to screen lines, 
 854                  * may be shuffled.
 855                  */
 856                 for (i = fr; i <= lr; ++i) {
 857                         /* Save new hash for next update. */
 858                         __m_screen->_hash[i] = nhash[i];
 859 
 860                         /* Mark line as untouched. */
 861                         newscr->_first[i] = newscr->_maxx;
 862                         newscr->_last[i] = -1;
 863                 }
 864         }
 865 }
 866 
 867 /*f
 868  * Simple screen update algorithm
 869  *
 870  * We perform a simple incremental update of the terminal screen. 
 871  * Only the segment of a line that was touched is replaced on the 
 872  * line.
 873  */
 874 STATIC void
 875 simple()
 876 {
 877         int row;
 878 
 879         for (row = 0; row < LINES; ++row) {
 880                 if (newscr->_first[row] < newscr->_last[row]) {
 881                         text_replace(row);
 882 
 883                         /* Mark line as untouched. */
 884                         newscr->_first[row] = newscr->_maxx;
 885                         newscr->_last[row] = -1;
 886 
 887                         if (__m_screen->_flags & S_INS_DEL_LINE)
 888                                 __m_cc_hash(newscr, nhash, row);
 889                 }
 890         }
 891 
 892         newscr->_flags &= ~W_REDRAW_WINDOW;
 893 }
 894 
 895 /*f
 896  * Send all changes made to _newscr to the physical terminal.
 897  *
 898  * If idlok() is set TRUE then doupdate will try and use hardware insert
 899  * and delete line sequences in an effort to optimize output.  idlok()
 900  * should really only be used in applications that want a proper scrolling
 901  * effect.
 902  * 
 903  * Added scroll heuristic to handle special case where a full size window
 904  * with full size scroll region, will scroll the window and replace dirty
 905  * lines instead of performing usual cost/script operations.
 906  */
 907 int 
 908 doupdate()
 909 {
 910 #ifdef SIGTSTP
 911         int (*oldsig)(int) = signal(SIGTSTP, SIG_IGN);
 912 #endif
 913 
 914 #ifdef M_CURSES_TYPEAHEAD
 915         unsigned char cc;
 916         int min, time, icanon;
 917 
 918         if (__m_screen->_flags & S_ISATTY) {
 919                 /* Set up non-blocking input for typeahead trapping. */
 920                 min = cur_term->_prog.c_cc[VMIN];
 921                 time = cur_term->_prog.c_cc[VTIME];
 922                 icanon = cur_term->_prog.c_lflag & ICANON;
 923 
 924                 cur_term->_prog.c_cc[VMIN] = 0;
 925                 cur_term->_prog.c_cc[VTIME] = 0;
 926                 cur_term->_prog.c_lflag &= ~ICANON;
 927 
 928                 (void) tcsetattr(__m_screen->_kfd, TCSANOW, &cur_term->_prog);
 929         }
 930 #endif /* M_CURSES_TYPEAHEAD */
 931 
 932 #ifdef M_CURSES_TRACE
 933         __m_trace(
 934                 "doupdate(void) using %s algorithm.", 
 935                 (__m_screen->_flags & S_INS_DEL_LINE) ? "complex" : "simple"
 936         );
 937 #endif
 938 
 939         newscr = __m_screen->_newscr;
 940 
 941         if (__m_screen->_flags & S_ENDWIN) {
 942                 /* Return from temporary escape done with endwin(). */
 943                 __m_screen->_flags &= ~S_ENDWIN;
 944 
 945                 (void) reset_prog_mode();
 946                 if (enter_ca_mode != (char *) 0)
 947                         (void) tputs(enter_ca_mode, 1, __m_outc);
 948                 if (keypad_xmit != (char *) 0)
 949                         (void) tputs(keypad_xmit, 1, __m_outc);
 950                 if (ena_acs != (char *) 0)
 951                         (void) tputs(ena_acs, 1, __m_outc);
 952 
 953                 /* Force redraw of screen. */
 954                 newscr->_flags |= W_CLEAR_WINDOW;
 955         }
 956 
 957 #ifdef M_CURSES_TYPEAHEAD
 958         if (setjmp(breakout) == 0) {
 959                 if ((__m_screen->_flags & S_ISATTY)
 960                 && read(__m_screen->_kfd, &cc, sizeof cc) == sizeof cc) {
 961                         (void) ungetch(cc);
 962                         longjmp(breakout, 1);
 963                 }
 964 #endif /* M_CURSES_TYPEAHEAD */
 965 
 966                 /* When redrawwing a window, we not only assume that line 
 967                  * noise may have lost characters, but line noise may have
 968                  * generated bogus characters on the screen outside the
 969                  * the window in question, in which case redraw the entire
 970                  * screen to be sure.
 971                  */
 972                 if (newscr->_flags & (W_CLEAR_WINDOW | W_REDRAW_WINDOW)) {
 973                         clear_bottom(0);
 974                         newscr->_flags &= ~W_CLEAR_WINDOW;
 975                         (void) wtouchln(newscr, 0, newscr->_maxy, 1);
 976                 }
 977 
 978                 if (newscr->_flags & W_REDRAW_WINDOW)
 979                         simple();
 980 #if 0           /* This first expression, of undefined section, is useless
 981                  * since newscr->_scroll is unsigned and never LT zero.
 982                  */
 983                 else if (newscr->_scroll < 0 && scroll_dn(-newscr->_scroll))
 984 #else
 985                 else if (scroll_dn(-newscr->_scroll))
 986 #endif
 987                         ;
 988                 else if (0 < newscr->_scroll && scroll_up(newscr->_scroll))
 989                         ;
 990                 else if (__m_screen->_flags & S_INS_DEL_LINE)
 991                         complex();
 992                 else
 993                         simple();
 994 
 995                 if (!(newscr->_flags & W_LEAVE_CURSOR))
 996                         GOTO(newscr->_cury, newscr->_curx);
 997 
 998                 if (!(curscr->_flags & W_FLUSH))
 999                         (void) fflush(__m_screen->_of);
1000 #ifdef M_CURSES_TYPEAHEAD
1001         }
1002 
1003         if (__m_screen->_flags & S_ISATTY) {
1004                 /* Restore previous input mode. */
1005                 cur_term->_prog.c_cc[VMIN] = min;
1006                 cur_term->_prog.c_cc[VTIME] = time;
1007                 cur_term->_prog.c_lflag |= icanon;
1008 
1009                 (void) tcsetattr(__m_screen->_kfd,TCSANOW,&cur_term->_prog);
1010         }
1011 #endif /* M_CURSES_TYPEAHEAD */
1012 
1013         newscr->_scroll = curscr->_scroll = 0;
1014 #ifdef SIGTSTP
1015         signal(SIGTSTP, oldsig);
1016 #endif
1017 
1018         return __m_return_code("doupdate", OK);
1019 }
1020 
1021 /*
1022  * If true, the implementation may use hardware insert and delete,
1023  * character features of the terminal.  The window parameter
1024  * is ignored.
1025  */
1026 void
1027 idcok(WINDOW *w, bool bf)
1028 {
1029 #ifdef M_CURSES_TRACE
1030         __m_trace("idcok(%p, %d)", w, bf);
1031 #endif
1032  
1033         __m_screen->_flags &= ~S_INS_DEL_CHAR;
1034         if (bf)
1035                 __m_screen->_flags |= S_INS_DEL_CHAR;
1036 
1037         __m_return_void("idcok");
1038 }
1039  
1040 /*
1041  * If true, the implementation may use hardware insert, delete,
1042  * and scroll line features of the terminal.  The window parameter
1043  * is ignored.
1044  */
1045 int
1046 idlok(WINDOW *w, bool bf)
1047 {
1048 #ifdef M_CURSES_TRACE
1049         __m_trace("idlok(%p, %d)", w, bf);
1050 #endif
1051 
1052         __m_screen->_flags &= ~S_INS_DEL_LINE;
1053         if (bf && has_il())
1054                 __m_screen->_flags |= S_INS_DEL_LINE;
1055 
1056         return __m_return_code("idlok", OK);
1057 }
1058 
1059 /*
1060  * Use the POSIX 32-bit CRC function to compute a hash value 
1061  * for the window line.
1062  */
1063 void
1064 __m_cc_hash(w, array, y)
1065 WINDOW *w;
1066 unsigned long *array;
1067 int y;
1068 {
1069         array[y] = 0;
1070         m_crcposix(
1071                 &array[y], (unsigned char *) w->_line[y], 
1072                 (size_t) (w->_maxx * sizeof **w->_line)
1073         );
1074 }
1075 
1076