ncurses 4.1
[ncurses.git] / ncurses / lib_doupdate.c
1 /***************************************************************************
2 *                            COPYRIGHT NOTICE                              *
3 ****************************************************************************
4 *                ncurses is copyright (C) 1992-1995                        *
5 *                          Zeyd M. Ben-Halim                               *
6 *                          zmbenhal@netcom.com                             *
7 *                          Eric S. Raymond                                 *
8 *                          esr@snark.thyrsus.com                           *
9 *                                                                          *
10 *        Permission is hereby granted to reproduce and distribute ncurses  *
11 *        by any means and for any fee, whether alone or as part of a       *
12 *        larger distribution, in source or in binary form, PROVIDED        *
13 *        this notice is included with any such distribution, and is not    *
14 *        removed from any of its header files. Mention of ncurses in any   *
15 *        applications linked with it is highly appreciated.                *
16 *                                                                          *
17 *        ncurses comes AS IS with no warranty, implied or expressed.       *
18 *                                                                          *
19 ***************************************************************************/
20
21
22 /*-----------------------------------------------------------------
23  *
24  *      lib_doupdate.c
25  *
26  *      The routine doupdate() and its dependents.  Also _nc_outstr(),
27  *      so all physical output is concentrated here (except _nc_outch()
28  *      in lib_tputs.c).
29  *
30  *-----------------------------------------------------------------*/
31
32 #include <curses.priv.h>
33
34 #if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES
35 #define USE_TRACE_TIMES 1
36 #else
37 #define USE_TRACE_TIMES 0
38 #endif
39
40 #if HAVE_SYS_TIME_H && ! SYSTEM_LOOKS_LIKE_SCO
41 #include <sys/time.h>
42 #endif
43
44 #if USE_TRACE_TIMES
45 #include <sys/times.h>
46 #endif
47
48 #if USE_FUNC_POLL
49 #include <stropts.h>
50 #include <poll.h>
51 #elif HAVE_SELECT
52 #if HAVE_SYS_SELECT_H
53 #include <sys/select.h>
54 #endif
55 #endif
56
57 #include <term.h>
58
59 MODULE_ID("$Id: lib_doupdate.c,v 1.60 1997/05/03 19:32:55 Alexander.V.Lukyanov Exp $")
60
61 /*
62  * This define controls the line-breakout optimization.  Every once in a
63  * while during screen refresh, we want to check for input and abort the
64  * update if there's some waiting.  CHECK_INTERVAL controls the number of
65  * changed lines to be emitted between input checks.
66  *
67  * Note: Input-check-and-abort is no longer done if the screen is being
68  * updated from scratch.  This is a feature, not a bug.
69  */
70 #define CHECK_INTERVAL  6
71
72 /*
73  * Enable checking to see if doupdate and friends are tracking the true
74  * cursor position correctly.  NOTE: this is a debugging hack which will
75  * work ONLY on ANSI-compatible terminals!
76  */
77 /* #define POSITION_DEBUG */
78
79 static inline chtype ClrBlank ( WINDOW *win );
80 static inline chtype ClrSetup ( WINDOW *scr );
81 static int ClrBottom(int total);
82 static int InsStr( chtype *line, int count );
83 static void ClearScreen( void );
84 static void ClrUpdate( WINDOW *scr );
85 static void DelChar( int count );
86 static void TransformLine( int const lineno );
87
88 #ifdef POSITION_DEBUG
89 /****************************************************************************
90  *
91  * Debugging code.  Only works on ANSI-standard terminals.
92  *
93  ****************************************************************************/
94
95 void position_check(int expected_y, int expected_x, char *legend)
96 /* check to see if the real cursor position matches the virtual */
97 {
98     static char  buf[9];
99     int y, x;
100
101     if (_nc_tracing)
102         return;
103
104     memset(buf, '\0', sizeof(buf));
105     (void) write(1, "\033[6n", 4);      /* only works on ANSI-compatibles */
106     (void) read(0, (void *)buf, 8);
107     _tracef("probe returned %s", _nc_visbuf(buf));
108
109     /* try to interpret as a position report */
110     if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2)
111         _tracef("position probe failed in %s", legend);
112     else if (y - 1 != expected_y || x - 1 != expected_x)
113         _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
114                 y-1, x-1, expected_y, expected_x, legend);
115     else
116         _tracef("position matches OK in %s", legend);
117 }
118 #endif /* POSITION_DEBUG */
119
120 /****************************************************************************
121  *
122  * Optimized update code
123  *
124  ****************************************************************************/
125
126 static inline void GoTo(int const row, int const col)
127 {
128         chtype  oldattr = SP->_current_attr;
129
130         TR(TRACE_MOVE, ("GoTo(%d, %d) from (%d, %d)",
131                         row, col, SP->_cursrow, SP->_curscol));
132
133 #ifdef POSITION_DEBUG
134         position_check(SP->_cursrow, SP->_curscol, "GoTo");
135 #endif /* POSITION_DEBUG */
136
137         /*
138          * Force restore even if msgr is on when we're in an alternate
139          * character set -- these have a strong tendency to screw up the
140          * CR & LF used for local character motions!
141          */
142         if ((oldattr & A_ALTCHARSET)
143             || (oldattr && !move_standout_mode))
144         {
145                 TR(TRACE_CHARPUT, ("turning off (%#lx) %s before move",
146                    oldattr, _traceattr(oldattr)));
147                 vidattr(A_NORMAL);
148         }
149
150         mvcur(SP->_cursrow, SP->_curscol, row, col);
151         SP->_cursrow = row;
152         SP->_curscol = col;
153 }
154
155 static inline void PutAttrChar(chtype ch)
156 {
157         if (tilde_glitch && (TextOf(ch) == '~'))
158                 ch = ('`' | AttrOf(ch));
159
160         TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
161                           _tracechtype(ch),
162                            SP->_cursrow, SP->_curscol));
163         UpdateAttrs(ch);
164         putc((int)TextOf(ch), SP->_ofp);
165 #ifdef TRACE
166         _nc_outchars++;
167 #endif /* TRACE */
168         SP->_curscol++;
169         if (char_padding) {
170                 TPUTS_TRACE("char_padding");
171                 putp(char_padding);
172         }
173 }
174
175 static bool check_pending(void)
176 /* check for pending input */
177 {
178         if (SP->_checkfd >= 0) {
179 #if USE_FUNC_POLL
180                 struct pollfd fds[1];
181                 fds[0].fd = SP->_checkfd;
182                 fds[0].events = POLLIN;
183                 if (poll(fds, 1, 0) > 0)
184                 {
185                         fflush(SP->_ofp);
186                         return TRUE;
187                 }
188 #elif HAVE_SELECT
189                 fd_set fdset;
190                 struct timeval ktimeout;
191
192                 ktimeout.tv_sec =
193                 ktimeout.tv_usec = 0;
194
195                 FD_ZERO(&fdset);
196                 FD_SET(SP->_checkfd, &fdset);
197                 if (select(SP->_checkfd+1, &fdset, NULL, NULL, &ktimeout) > 0)
198                 {
199                         fflush(SP->_ofp);
200                         return TRUE;
201                 }
202 #endif
203         }
204         return FALSE;
205 }
206
207 /*
208  * No one supports recursive inline functions.  However, gcc is quieter if we
209  * instantiate the recursive part separately.
210  */
211 #if CC_HAS_INLINE_FUNCS
212 static void callPutChar(chtype const);
213 #else
214 #define callPutChar(ch) PutChar(ch)
215 #endif
216
217 static inline void PutChar(chtype const ch)
218 /* insert character, handling automargin stuff */
219 {
220     if (!(SP->_cursrow == screen_lines-1 && SP->_curscol == screen_columns-1
221                 && auto_right_margin && !eat_newline_glitch))
222     {
223         PutAttrChar(ch);        /* normal case */
224     }
225     else if (!auto_right_margin /* maybe we can suppress automargin */
226              || (enter_am_mode && exit_am_mode))
227     {
228         bool old_am = auto_right_margin;
229
230         if (old_am)
231         {
232             TPUTS_TRACE("exit_am_mode");
233             putp(exit_am_mode);
234         }
235         PutAttrChar(ch);
236         if (old_am)
237         {
238             TPUTS_TRACE("enter_am_mode");
239             putp(enter_am_mode);
240         }
241     }
242     else
243     {
244         GoTo(screen_lines-1,screen_columns-2);
245         callPutChar(ch);
246         GoTo(screen_lines-1,screen_columns-2);
247         if (InsStr(newscr->_line[screen_lines-1].text+screen_columns-2,1)==ERR)
248             return;
249     }
250
251     if (SP->_curscol >= screen_columns)
252     {
253         if (eat_newline_glitch)
254         {
255             /*
256              * xenl can manifest two different ways.  The vt100
257              * way is that, when you'd expect the cursor to wrap,
258              * it stays hung at the right margin (on top of the
259              * character just emitted) and doesn't wrap until the
260              * *next* graphic char is emitted.  The c100 way is
261              * to ignore LF received just after an am wrap.
262              *
263              * An aggressive way to handle this would be to
264              * emit CR/LF after the char and then assume the wrap
265              * is done, you're on the first position of the next
266              * line, and the terminal out of its weird state.
267              * Here it's safe to just tell the code that the
268              * cursor is in hyperspace and let the next mvcur()
269              * call straighten things out.
270              */
271             SP->_curscol = -1;
272             SP->_cursrow = -1;
273         }
274         else if (auto_right_margin)
275         {
276             SP->_curscol = 0;
277             SP->_cursrow++;
278         }
279         else
280         {
281             SP->_curscol--;
282         }
283     }
284 #ifdef POSITION_DEBUG
285     position_check(SP->_cursrow, SP->_curscol, "PutChar");
286 #endif /* POSITION_DEBUG */
287 }
288
289 /*
290  * Issue a given span of characters from an array.
291  * Must be functionally equivalent to:
292  *      for (i = 0; i < num; i++)
293  *          PutChar(ntext[i]);
294  * but can leave the cursor positioned at the middle of the interval.
295  *
296  * Returns: 0 - cursor is at the end of interval
297  *          1 - cursor is somewhere in the middle
298  *
299  * This code is optimized using ech and rep.
300  */
301 static inline int EmitRange(const chtype *ntext, int num)
302 {
303     int i;
304
305     if (erase_chars || repeat_char)
306     {
307         while (num > 0)
308         {
309             int runcount;
310             chtype ntext0;
311
312             while (num>1 && ntext[0]!=ntext[1])
313             {
314                 PutChar(ntext[0]);
315                 ntext++;
316                 num--;
317             }
318             ntext0 = ntext[0];
319             if (num==1)
320             {
321                 PutChar(ntext0);
322                 return 0;
323             }
324             runcount = 2;
325
326             while (runcount < num && ntext[runcount] == ntext0)
327                 runcount++;
328
329             /*
330              * The cost expression in the middle isn't exactly right.
331              * _cup_cost is an upper bound on the cost for moving to the
332              * end of the erased area, but not the cost itself (which we
333              * can't compute without emitting the move).  This may result
334              * in erase_chars not getting used in some situations for
335              * which it would be marginally advantageous.
336              */
337             if (erase_chars
338                 && runcount > SP->_ech_cost + SP->_cup_cost
339                 && can_clear_with(ntext0))
340             {
341                 UpdateAttrs(ntext0);
342                 putp(tparm(erase_chars, runcount));
343
344                 /*
345                  * If this is the last part of the given interval,
346                  * don't bother moving cursor, since it can be the
347                  * last update on the line.
348                  */
349                 if (runcount < num)
350                     GoTo(SP->_cursrow, SP->_curscol + runcount);
351                 else
352                     return 1;   /* cursor stays in the middle */
353             }
354             else if (repeat_char && runcount > SP->_rep_cost)
355             {
356                 bool wrap_possible = (SP->_curscol + runcount >= screen_columns);
357                 int rep_count = runcount;
358
359                 if (wrap_possible)
360                     rep_count--;
361
362                 UpdateAttrs(ntext0);
363                 putp(tparm(repeat_char, TextOf(ntext0), rep_count));
364                 SP->_curscol += rep_count;
365
366                 if (wrap_possible)
367                     PutChar(ntext0);
368             }
369             else
370             {
371                 for (i = 0; i < runcount; i++)
372                     PutChar(ntext[i]);
373             }
374             ntext += runcount;
375             num -= runcount;
376         }
377         return 0;
378     }
379
380     for (i = 0; i < num; i++)
381         PutChar(ntext[i]);
382     return 0;
383 }
384
385 /*
386  * Output the line in the given range [first .. last]
387  *
388  * If there's a run of identical characters that's long enough to justify
389  * cursor movement, use that also.
390  *
391  * Returns: same as EmitRange
392  */
393 static int PutRange(
394         const chtype *otext,
395         const chtype *ntext,
396         int row,
397         int first, int last)
398 {
399         int j, run;
400         int cost = min(SP->_cup_ch_cost, SP->_hpa_ch_cost);
401
402         TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)",
403                          otext, ntext, row, first, last));
404
405         if (otext != ntext
406          && (last-first+1) > cost) {
407                 for (j = first, run = 0; j <= last; j++) {
408                         if (otext[j] == ntext[j]) {
409                                 run++;
410                         } else {
411                                 if (run > cost) {
412                                         int before_run = (j - run);
413                                         EmitRange(ntext+first, before_run-first);
414                                         GoTo(row, first = j);
415                                 }
416                                 run = 0;
417                         }
418                 }
419         }
420         return EmitRange(ntext + first, last-first+1);
421 }
422
423 #if CC_HAS_INLINE_FUNCS
424 static void callPutChar(chtype const ch)
425 {
426         PutChar(ch);
427 }
428 #endif
429
430 #define MARK_NOCHANGE(win,row) \
431         { \
432                 win->_line[row].firstchar = _NOCHANGE; \
433                 win->_line[row].lastchar = _NOCHANGE; \
434                 win->_line[row].oldindex = row; \
435         }
436
437 int doupdate(void)
438 {
439 int     i;
440 int     nonempty;
441 #if USE_TRACE_TIMES
442 struct tms before, after;
443 #endif /* USE_TRACE_TIMES */
444
445         T((T_CALLED("doupdate()")));
446
447 #ifdef TRACE
448         if (_nc_tracing & TRACE_UPDATE)
449         {
450             if (curscr->_clear)
451                 _tracef("curscr is clear");
452             else
453                 _tracedump("curscr", curscr);
454             _tracedump("newscr", newscr);
455         }
456 #endif /* TRACE */
457
458         _nc_signal_handler(FALSE);
459
460         if (SP->_endwin == TRUE) {
461
462                 T(("coming back from shell mode"));
463                 reset_prog_mode();
464
465                 /*
466                  * This is a transparent extension:  XSI does not address it,
467                  * and applications need not know that ncurses can do it. 
468                  *
469                  * Check if the terminal size has changed while curses was off
470                  * (this can happen in an xterm, for example), and resize the
471                  * ncurses data structures accordingly.
472                  */
473                 _nc_get_screensize();
474                 resizeterm(LINES, COLS);
475
476                 _nc_mvcur_resume();
477                 _nc_mouse_resume(SP);
478                 newscr->_clear = TRUE;
479                 SP->_endwin = FALSE;
480         }
481
482 #if USE_TRACE_TIMES
483         /* zero the metering machinery */
484         _nc_outchars = 0;
485         (void) times(&before);
486 #endif /* USE_TRACE_TIMES */
487
488         /*
489          * This is the support for magic-cookie terminals.  The
490          * theory: we scan the virtual screen looking for attribute
491          * turnons.  Where we find one, check to make sure it's
492          * realizable by seeing if the required number of
493          * un-attributed blanks are present before and after the
494          * attributed range; try to shift the range boundaries over
495          * blanks (not changing the screen display) so this becomes
496          * true.  If it is, shift the beginning attribute change
497          * appropriately (the end one, if we've gotten this far, is
498          * guaranteed room for its cookie). If not, nuke the added
499          * attributes out of the span.
500          */
501         if (magic_cookie_glitch > 0) {
502             int j, k;
503             attr_t rattr = A_NORMAL;
504
505             for (i = 0; i < screen_lines; i++)
506                 for (j = 0; j < screen_columns; j++)
507                 {
508                     bool failed = FALSE;
509                     chtype turnon = AttrOf(newscr->_line[i].text[j]) & ~rattr;
510
511                     /* is an attribute turned on here? */
512                     if (turnon == 0)
513                         continue;
514
515                     T(("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
516                     T(("...to %s",_traceattr(turnon)));
517
518                     /*
519                      * If the attribute change location is a blank with a
520                      * "safe" attribute, undo the attribute turnon.  This may
521                      * ensure there's enough room to set the attribute before
522                      * the first non-blank in the run.
523                      */
524 #define SAFE(a) !((a) & ~NONBLANK_ATTR)
525                     if (TextOf(newscr->_line[i].text[j])==' ' && SAFE(turnon))
526                     {
527                         newscr->_line[i].text[j] &= ~turnon;
528                         continue;
529                     }
530
531                     /* check that there's enough room at start of span */
532                     for (k = 1; k <= magic_cookie_glitch; k++)
533                         if (j-k < 0
534                                 || TextOf(newscr->_line[i].text[j-k]) != ' '
535                                 || !SAFE(AttrOf(newscr->_line[i].text[j-k])))
536                             failed = TRUE;
537                     if (!failed)
538                     {
539                         bool    end_onscreen = FALSE;
540                         int     m, n = -1;
541
542                         /* find end of span, if it's onscreen */
543                         for (m = i; m < screen_lines; m++)
544                             for (n = j; n < screen_columns; n++)
545                                 if (AttrOf(newscr->_line[m].text[n]) == rattr)
546                                 {
547                                     end_onscreen = TRUE;
548                                     T(("Range attributed with %s ends at (%d, %d)",
549                                        _traceattr(turnon),m,n));
550                                     goto foundit;
551                                 }
552                         T(("Range attributed with %s ends offscreen",
553                             _traceattr(turnon)));
554                     foundit:;
555
556                         if (end_onscreen)
557                         {
558                             chtype      *lastline = newscr->_line[m].text;
559
560                             /*
561                              * If there are safely-attributed blanks at the
562                              * end of the range, shorten the range.  This will
563                              * help ensure that there is enough room at end
564                              * of span.
565                              */
566                             while (n >= 0
567                                    && TextOf(lastline[n]) == ' '
568                                    && SAFE(AttrOf(lastline[n])))
569                                 lastline[n--] &=~ turnon;
570
571                             /* check that there's enough room at end of span */
572                             for (k = 1; k <= magic_cookie_glitch; k++)
573                                 if (n + k >= screen_columns
574                                         || TextOf(lastline[n + k]) != ' '
575                                         || !SAFE(AttrOf(lastline[n+k])))
576                                     failed = TRUE;
577                         }
578                     }
579
580                     if (failed)
581                     {
582                         int p, q;
583
584                         T(("Clearing %s beginning at (%d, %d)",
585                                                 _traceattr(turnon), i, j));
586
587                         /* turn off new attributes over span */
588                         for (p = i; p < screen_lines; p++)
589                             for (q = j; q < screen_columns; q++)
590                                 if (AttrOf(newscr->_line[p].text[q]) == rattr)
591                                     goto foundend;
592                                 else
593                                     newscr->_line[p].text[q] &=~ turnon;
594                     foundend:;
595                     }
596                     else
597                     {
598                         T(("Cookie space for %s found before (%d, %d)",
599                                                 _traceattr(turnon), i, j));
600
601                         /*
602                          * back up the start of range so there's room
603                          * for cookies before the first nonblank character
604                          */
605                         for (k = 1; k <= magic_cookie_glitch; k++)
606                             newscr->_line[i].text[j-k] |= turnon;
607                     }
608
609                     rattr = AttrOf(newscr->_line[i].text[j]);
610                 }
611
612 #ifdef TRACE
613             /* show altered highlights after magic-cookie check */
614             if (_nc_tracing & TRACE_UPDATE)
615             {
616                 _tracef("After magic-cookie check...");
617                 _tracedump("newscr", newscr);
618             }
619 #endif /* TRACE */
620         }
621
622         nonempty = 0;
623         if (curscr->_clear) {           /* force refresh ? */
624                 T(("clearing and updating curscr"));
625                 ClrUpdate(newscr);      /* yes, clear all & update */
626                 curscr->_clear = FALSE; /* reset flag */
627         } else if (newscr->_clear) {
628                 T(("clearing and updating newscr"));
629                 ClrUpdate(newscr);
630                 newscr->_clear = FALSE;
631         } else {
632                 int changedlines;
633
634                 nonempty = min(screen_lines, newscr->_maxy+1);
635 #if 0           /* still 5% slower 960928 */
636 #if defined(TRACE) || defined(NCURSES_TEST)
637                 if (_nc_optimize_enable & OPTIMIZE_HASHMAP)
638 #endif /*TRACE */
639                         _nc_hash_map();
640 #endif
641 #if defined(TRACE) || defined(NCURSES_TEST)
642                 if (_nc_optimize_enable & OPTIMIZE_SCROLL)
643 #endif /*TRACE */
644                         _nc_scroll_optimize();
645
646                 if (clr_eos)
647                         nonempty = ClrBottom(nonempty);
648
649                 T(("Transforming lines, nonempty %d", nonempty));
650                 for (i = changedlines = 0; i < nonempty; i++) {
651                         /*
652                          * newscr->line[i].firstchar is normally set
653                          * by wnoutrefresh.  curscr->line[i].firstchar
654                          * is normally set by _nc_scroll_window in the
655                          * vertical-movement optimization code,
656                          */
657                         if (newscr->_line[i].firstchar != _NOCHANGE
658                          || curscr->_line[i].firstchar != _NOCHANGE)
659                         {
660                                 TransformLine(i);
661                                 changedlines++;
662                         }
663
664                         /* mark line changed successfully */
665                         if (i <= newscr->_maxy)
666                                 MARK_NOCHANGE(newscr,i)
667                         if (i <= curscr->_maxy)
668                                 MARK_NOCHANGE(curscr,i)
669
670                         /*
671                          * Here is our line-breakout optimization.
672                          */
673                         if ((changedlines % CHECK_INTERVAL) == CHECK_INTERVAL-1
674                          && check_pending())
675                                 goto cleanup;
676                 }
677         }
678
679         /* put everything back in sync */
680         for (i = nonempty; i <= newscr->_maxy; i++)
681                 MARK_NOCHANGE(newscr,i)
682         for (i = nonempty; i <= curscr->_maxy; i++)
683                 MARK_NOCHANGE(curscr,i)
684
685         curscr->_curx = newscr->_curx;
686         curscr->_cury = newscr->_cury;
687
688         GoTo(curscr->_cury, curscr->_curx);
689
690     cleanup:
691         /*
692          * Keep the physical screen in normal mode in case we get other
693          * processes writing to the screen.
694          */
695         UpdateAttrs(A_NORMAL);
696
697         fflush(SP->_ofp);
698         curscr->_attrs = newscr->_attrs;
699 /*      curscr->_bkgd  = newscr->_bkgd; */
700
701 #if USE_TRACE_TIMES
702         (void) times(&after);
703         TR(TRACE_TIMES, ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
704             _nc_outchars,
705             after.tms_stime-before.tms_stime,
706             after.tms_utime-before.tms_utime));
707 #endif /* USE_TRACE_TIMES */
708
709         _nc_signal_handler(TRUE);
710
711         returnCode(OK);
712 }
713
714 /*
715  *      ClrBlank(win)
716  *
717  *      Returns the attributed character that corresponds to the "cleared"
718  *      screen.  If the terminal has the back-color-erase feature, this will be
719  *      colored according to the wbkgd() call.  (Other attributes are
720  *      unspecified, hence assumed to be reset in accordance with
721  *      'ClrSetup()').
722  *
723  *      We treat 'curscr' specially because it isn't supposed to be set directly
724  *      in the wbkgd() call.  Assume 'stdscr' for this case.
725  */
726 #define BCE_ATTRS (A_NORMAL|A_COLOR)
727 #define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_bkgd)
728
729 static inline chtype ClrBlank (WINDOW *win)
730 {
731 chtype  blank = BLANK;
732         if (back_color_erase)
733                 blank |= (BCE_BKGD(win) & BCE_ATTRS);
734         return blank;
735 }
736
737 /*
738  *      ClrSetup(win)
739  *
740  *      Ensures that if the terminal recognizes back-color-erase, that we
741  *      set the video attributes to match the window's background color
742  *      before an erase operation.
743  */
744 static inline chtype ClrSetup (WINDOW *win)
745 {
746         if (back_color_erase)
747                 vidattr(BCE_BKGD(win) & BCE_ATTRS);
748         return ClrBlank(win);
749 }
750
751 /*
752 **      ClrUpdate(scr)
753 **
754 **      Update by clearing and redrawing the entire screen.
755 **
756 */
757
758 static void ClrUpdate(WINDOW *scr)
759 {
760 int     i = 0, j = 0;
761 int     lastNonBlank;
762 chtype  blank = ClrSetup(scr);
763
764         T(("ClrUpdate(%p) called", scr));
765         ClearScreen();
766
767         if (scr != curscr) {
768                 for (i = 0; i < screen_lines ; i++)
769                         for (j = 0; j < screen_columns; j++)
770                                 curscr->_line[i].text[j] = blank;
771         }
772
773         T(("updating screen from scratch"));
774         for (i = 0; i < min(screen_lines, scr->_maxy + 1); i++) {
775                 lastNonBlank = scr->_maxx;
776
777                 while (lastNonBlank >= 0
778                   &&   scr->_line[i].text[lastNonBlank] == blank)
779                         lastNonBlank--;
780
781                 if (lastNonBlank >= 0) {
782                         if (lastNonBlank > screen_columns)
783                                 lastNonBlank = screen_columns;
784                         GoTo(i, 0);
785                         PutRange(curscr->_line[i].text,
786                                     scr->_line[i].text, i, 0, lastNonBlank);
787                 }
788         }
789
790         if (scr != curscr) {
791                 for (i = 0; i < screen_lines ; i++)
792                         memcpy(curscr->_line[i].text,
793                                   scr->_line[i].text,
794                                   screen_columns * sizeof(chtype));
795         }
796 }
797
798 /*
799 **      ClrToEOL(blank)
800 **
801 **      Clear to end of current line, starting at the cursor position
802 */
803
804 static void ClrToEOL(chtype blank)
805 {
806 int     j;
807 bool    needclear = FALSE;
808
809         for (j = SP->_curscol; j < screen_columns; j++)
810         {
811             chtype *cp = &(curscr->_line[SP->_cursrow].text[j]);
812
813             if (*cp != blank)
814             {
815                 *cp = blank;
816                 needclear = TRUE;
817             }
818         }
819
820         if (needclear)
821         {
822             UpdateAttrs(blank);
823             TPUTS_TRACE("clr_eol");
824             if (SP->_el_cost > (screen_columns - SP->_curscol))
825             {
826                 int count = (screen_columns - SP->_curscol);
827                 while (count-- > 0)
828                         PutChar(blank);
829             }
830             else
831                 putp(clr_eol);
832         }
833 }
834
835 /*
836  *      ClrBottom(total)
837  *
838  *      Test if clearing the end of the screen would satisfy part of the
839  *      screen-update.  Do this by scanning backwards through the lines in the
840  *      screen, checking if each is blank, and one or more are changed.
841  */
842 static int ClrBottom(int total)
843 {
844 static  chtype  *tstLine;
845 static  size_t  lenLine;
846
847 int     row, col;
848 int     top    = total;
849 int     last   = min(screen_columns, newscr->_maxx+1);
850 size_t  length = sizeof(chtype) * last;
851 chtype  blank  = newscr->_line[total-1].text[last-1]; /* lower right char */
852
853         if(!can_clear_with(blank))
854                 return total;
855
856         if (tstLine == 0)
857                 tstLine = (chtype *)malloc(length);
858         else if (length > lenLine)
859                 tstLine = (chtype *)realloc(tstLine, length);
860
861         if (tstLine != 0) {
862                 lenLine = length;
863                 for (col = 0; col < last; col++)
864                         tstLine[col] = blank;
865
866                 for (row = total-1; row >= 0; row--) {
867                         if (memcmp(tstLine, newscr->_line[row].text, length))
868                                 break;
869                         if (newscr->_line[row].firstchar != _NOCHANGE)
870                                 top = row;
871                 }
872
873                 if (top < total) {
874                         GoTo(top,0);
875                         UpdateAttrs(blank);
876                         TPUTS_TRACE("clr_eos");
877                         putp(clr_eos);
878                         while (total-- > top) {
879                                 for (col = 0; col <= curscr->_maxx; col++)
880                                         curscr->_line[total].text[col] = blank;
881                         }
882                         total++;
883                 }
884         }
885 #if NO_LEAKS
886         FreeAndNull(tstLine);
887 #endif
888         return total;
889 }
890
891
892 /*
893 **      TransformLine(lineno)
894 **
895 **      Transform the given line in curscr to the one in newscr, using
896 **      Insert/Delete Character if _nc_idcok && has_ic().
897 **
898 **              firstChar = position of first different character in line
899 **              oLastChar = position of last different character in old line
900 **              nLastChar = position of last different character in new line
901 **
902 **              move to firstChar
903 **              overwrite chars up to min(oLastChar, nLastChar)
904 **              if oLastChar < nLastChar
905 **                      insert newLine[oLastChar+1..nLastChar]
906 **              else
907 **                      delete oLastChar - nLastChar spaces
908 */
909
910 static void TransformLine(int const lineno)
911 {
912 int     firstChar, oLastChar, nLastChar;
913 chtype  *newLine = newscr->_line[lineno].text;
914 chtype  *oldLine = curscr->_line[lineno].text;
915 int     n;
916 bool    attrchanged = FALSE;
917
918         T(("TransformLine(%d) called", lineno));
919
920         if(ceol_standout_glitch && clr_eol) {
921                 firstChar = 0;
922                 while(firstChar < screen_columns) {
923                         if(AttrOf(newLine[firstChar]) != AttrOf(oldLine[firstChar]))
924                                 attrchanged = TRUE;
925                         firstChar++;
926                 }
927         }
928
929         firstChar = 0;
930
931         if (attrchanged) {      /* we may have to disregard the whole line */
932                 GoTo(lineno, firstChar);
933                 ClrToEOL(ClrBlank(curscr));
934                 PutRange(oldLine, newLine, lineno, 0, (screen_columns-1));
935         } else {
936                 chtype blank;
937
938                 /* find the first differing character */
939                 while (firstChar < screen_columns  &&
940                                 newLine[firstChar] == oldLine[firstChar])
941                         firstChar++;
942
943                 /* if there wasn't one, we're done */
944                 if (firstChar >= screen_columns)
945                         return;
946
947                 /* it may be cheap to clear leading whitespace with clr_bol */
948                 if (clr_bol && can_clear_with(blank=newLine[0]))
949                 {
950                         int oFirstChar, nFirstChar;
951
952                         for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++)
953                                 if (oldLine[oFirstChar] != blank)
954                                         break;
955                         for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++)
956                                 if (newLine[nFirstChar] != blank)
957                                         break;
958
959                         if (nFirstChar > oFirstChar + SP->_el1_cost)
960                         {
961                             GoTo(lineno, nFirstChar - 1);
962                             UpdateAttrs(blank);
963                             TPUTS_TRACE("clr_bol");
964                             putp(clr_bol);
965
966                             while (firstChar < nFirstChar)
967                                 oldLine[firstChar++] = blank;
968
969                             if (firstChar >= screen_columns)
970                                 return;
971                         }
972                 }
973
974                 blank = newLine[screen_columns-1];
975
976                 if(!can_clear_with(blank))
977                 {
978                         /* find the last differing character */
979                         nLastChar = screen_columns - 1;
980                         
981                         while (nLastChar > firstChar
982                          && newLine[nLastChar] == oldLine[nLastChar])
983                                 nLastChar--;
984
985                         if (nLastChar >= firstChar) {
986                                 GoTo(lineno, firstChar);
987                                 PutRange(oldLine, newLine, lineno, firstChar, nLastChar);
988                                 memcpy( oldLine + firstChar,
989                                         newLine + firstChar,
990                                         (nLastChar - firstChar + 1) * sizeof(chtype));
991                         }
992                         return;
993                 }
994
995                 /* find last non-blank character on old line */
996                 oLastChar = screen_columns - 1;
997                 while (oLastChar > firstChar  &&  oldLine[oLastChar] == blank)
998                         oLastChar--;
999
1000                 /* find last non-blank character on new line */
1001                 nLastChar = screen_columns - 1;
1002                 while (nLastChar > firstChar  &&  newLine[nLastChar] == blank)
1003                         nLastChar--;
1004
1005                 if((nLastChar == firstChar)
1006                  && (SP->_el_cost < (screen_columns - nLastChar))) {
1007                         GoTo(lineno, firstChar);
1008                         ClrToEOL(blank);
1009                         if(newLine[firstChar] != blank )
1010                                 PutChar(newLine[firstChar]);
1011                 } else if( newLine[nLastChar] != oldLine[oLastChar]
1012                                 || !(_nc_idcok && has_ic()) ) {
1013                         GoTo(lineno, firstChar);
1014                         if ((oLastChar - nLastChar) > SP->_el_cost) {
1015                                 if(PutRange(oldLine, newLine, lineno, firstChar, nLastChar))
1016                                     GoTo(lineno, nLastChar+1);
1017                                 ClrToEOL(blank);
1018                         } else {
1019                                 n = max( nLastChar , oLastChar );
1020                                 PutRange(oldLine, newLine, lineno, firstChar, n);
1021                         }
1022                 } else {
1023                         int nLastNonblank = nLastChar;
1024                         int oLastNonblank = oLastChar;
1025
1026                         /* find the last characters that really differ */
1027                         while (newLine[nLastChar] == oldLine[oLastChar]) {
1028                                 if (nLastChar != 0
1029                                  && oLastChar != 0) {
1030                                         nLastChar--;
1031                                         oLastChar--;
1032                                  } else {
1033                                         break;
1034                                  }
1035                         }
1036
1037                         n = min(oLastChar, nLastChar);
1038                         if (n >= firstChar) {
1039                                 GoTo(lineno, firstChar);
1040                                 PutRange(oldLine, newLine, lineno, firstChar, n);
1041                         }
1042                         GoTo(lineno, n+1);
1043
1044                         if (oLastChar < nLastChar) {
1045                                 int m = max(nLastNonblank, oLastNonblank);
1046                                 if (InsCharCost(nLastChar - oLastChar)
1047                                  > (m - n)) {
1048                                         PutRange(oldLine, newLine, lineno, n+1, m);
1049                                 } else {
1050                                         InsStr(&newLine[n+1], nLastChar - oLastChar);
1051                                 }
1052                         } else if (oLastChar > nLastChar ) {
1053                                 if (DelCharCost(oLastChar - nLastChar)
1054                                     > SP->_el_cost + nLastNonblank - (n+1)) {
1055                                         if(PutRange(oldLine, newLine, lineno,
1056                                                         n+1, nLastNonblank))
1057                                                 GoTo(lineno, nLastNonblank+1);
1058                                         ClrToEOL(blank);
1059                                 } else {
1060                                         /*
1061                                          * The delete-char sequence will
1062                                          * effectively shift in blanks from the
1063                                          * right margin of the screen.  Ensure
1064                                          * that they are the right color by
1065                                          * setting the video attributes from
1066                                          * the last character on the row.
1067                                          */
1068                                         UpdateAttrs(blank);
1069                                         DelChar(oLastChar - nLastChar);
1070                                 }
1071                         }
1072                 }
1073         }
1074
1075         /* update the code's internal representation */
1076         if (screen_columns > firstChar)
1077                 memcpy( oldLine + firstChar,
1078                         newLine + firstChar,
1079                         (screen_columns - firstChar) * sizeof(chtype));
1080 }
1081
1082 /*
1083 **      ClearScreen()
1084 **
1085 **      Clear the physical screen and put cursor at home
1086 **
1087 */
1088
1089 static void ClearScreen(void)
1090 {
1091
1092         T(("ClearScreen() called"));
1093
1094         if (clear_screen) {
1095                 TPUTS_TRACE("clear_screen");
1096                 putp(clear_screen);
1097                 SP->_cursrow = SP->_curscol = 0;
1098 #ifdef POSITION_DEBUG
1099                 position_check(SP->_cursrow, SP->_curscol, "ClearScreen");
1100 #endif /* POSITION_DEBUG */
1101         } else if (clr_eos) {
1102                 SP->_cursrow = SP->_curscol = -1;
1103                 GoTo(0,0);
1104
1105                 TPUTS_TRACE("clr_eos");
1106                 putp(clr_eos);
1107         } else if (clr_eol) {
1108                 SP->_cursrow = SP->_curscol = -1;
1109
1110                 while (SP->_cursrow < screen_lines) {
1111                         GoTo(SP->_cursrow, 0);
1112                         TPUTS_TRACE("clr_eol");
1113                         putp(clr_eol);
1114                 }
1115                 GoTo(0,0);
1116         }
1117         T(("screen cleared"));
1118 }
1119
1120
1121 /*
1122 **      InsStr(line, count)
1123 **
1124 **      Insert the count characters pointed to by line.
1125 **
1126 */
1127
1128 static int InsStr(chtype *line, int count)
1129 {
1130         T(("InsStr(%p,%d) called", line, count));
1131
1132         if (enter_insert_mode  &&  exit_insert_mode) {
1133                 TPUTS_TRACE("enter_insert_mode");
1134                 putp(enter_insert_mode);
1135                 while (count) {
1136                         PutAttrChar(*line);
1137                         line++;
1138                         count--;
1139                 }
1140                 TPUTS_TRACE("exit_insert_mode");
1141                 putp(exit_insert_mode);
1142                 return(OK);
1143         } else if (parm_ich) {
1144                 TPUTS_TRACE("parm_ich");
1145                 tputs(tparm(parm_ich, count), count, _nc_outch);
1146                 while (count) {
1147                         PutAttrChar(*line);
1148                         line++;
1149                         count--;
1150                 }
1151                 return(OK);
1152         } else {
1153                 while (count) {
1154                         TPUTS_TRACE("insert_character");
1155                         putp(insert_character);
1156                         PutAttrChar(*line);
1157                         if (insert_padding)
1158                         {
1159                                 TPUTS_TRACE("insert_padding");
1160                                 putp(insert_padding);
1161                         }
1162                         line++;
1163                         count--;
1164                 }
1165                 return(OK);
1166         }
1167 }
1168
1169 /*
1170 **      DelChar(count)
1171 **
1172 **      Delete count characters at current position
1173 **
1174 */
1175
1176 static void DelChar(int count)
1177 {
1178         T(("DelChar(%d) called, position = (%d,%d)", count, newscr->_cury, newscr->_curx));
1179
1180         if (parm_dch) {
1181                 TPUTS_TRACE("parm_dch");
1182                 tputs(tparm(parm_dch, count), count, _nc_outch);
1183         } else {
1184                 while (count--)
1185                 {
1186                         TPUTS_TRACE("delete_character");
1187                         putp(delete_character);
1188                 }
1189         }
1190 }
1191
1192 /*
1193 **      _nc_outstr(char *str)
1194 **
1195 **      Emit a string without waiting for update.
1196 */
1197
1198 void _nc_outstr(const char *str)
1199 {
1200     FILE *ofp = SP ? SP->_ofp : stdout;
1201
1202     (void) fputs(str, ofp);
1203     (void) fflush(ofp);
1204
1205 #ifdef TRACE
1206     _nc_outchars += strlen(str);
1207 #endif /* TRACE */
1208 }