ncurses 5.4
[ncurses.git] / ncurses / tty / tty_update.c
1 /****************************************************************************
2  * Copyright (c) 1998-2003,2004 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  *     and: Thomas E. Dickey 1996-2004                                      *
33  ****************************************************************************/
34
35 /*-----------------------------------------------------------------
36  *
37  *      lib_doupdate.c
38  *
39  *      The routine doupdate() and its dependents.
40  *      All physical output is concentrated here (except _nc_outch()
41   *     in lib_tputs.c).
42  *
43  *-----------------------------------------------------------------*/
44
45 #include <curses.priv.h>
46
47 #ifdef __BEOS__
48 #undef false
49 #undef true
50 #include <OS.h>
51 #endif
52
53 #if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES
54 #define USE_TRACE_TIMES 1
55 #else
56 #define USE_TRACE_TIMES 0
57 #endif
58
59 #if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT
60 #include <sys/time.h>
61 #endif
62
63 #if USE_TRACE_TIMES
64 #include <sys/times.h>
65 #endif
66
67 #if USE_FUNC_POLL
68 #elif HAVE_SELECT
69 #if HAVE_SYS_SELECT_H
70 #include <sys/select.h>
71 #endif
72 #endif
73
74 #include <ctype.h>
75 #include <term.h>
76
77 MODULE_ID("$Id: tty_update.c,v 1.205 2004/02/07 18:02:42 tom Exp $")
78
79 /*
80  * This define controls the line-breakout optimization.  Every once in a
81  * while during screen refresh, we want to check for input and abort the
82  * update if there's some waiting.  CHECK_INTERVAL controls the number of
83  * changed lines to be emitted between input checks.
84  *
85  * Note: Input-check-and-abort is no longer done if the screen is being
86  * updated from scratch.  This is a feature, not a bug.
87  */
88 #define CHECK_INTERVAL  5
89
90 #define FILL_BCE() (SP->_coloron && !SP->_default_color && !back_color_erase)
91
92 /*
93  * Enable checking to see if doupdate and friends are tracking the true
94  * cursor position correctly.  NOTE: this is a debugging hack which will
95  * work ONLY on ANSI-compatible terminals!
96  */
97 /* #define POSITION_DEBUG */
98
99 static inline NCURSES_CH_T ClrBlank(WINDOW *win);
100 static int ClrBottom(int total);
101 static void ClearScreen(NCURSES_CH_T blank);
102 static void ClrUpdate(void);
103 static void DelChar(int count);
104 static void InsStr(NCURSES_CH_T * line, int count);
105 static void TransformLine(int const lineno);
106
107 #ifdef POSITION_DEBUG
108 /****************************************************************************
109  *
110  * Debugging code.  Only works on ANSI-standard terminals.
111  *
112  ****************************************************************************/
113
114 static void
115 position_check(int expected_y, int expected_x, char *legend)
116 /* check to see if the real cursor position matches the virtual */
117 {
118     char buf[20];
119     char *s;
120     int y, x;
121
122     if (!_nc_tracing || (expected_y < 0 && expected_x < 0))
123         return;
124
125     _nc_flush();
126     memset(buf, '\0', sizeof(buf));
127     putp("\033[6n");            /* only works on ANSI-compatibles */
128     _nc_flush();
129     *(s = buf) = 0;
130     do {
131         int ask = sizeof(buf) - 1 - (s - buf);
132         int got = read(0, s, ask);
133         if (got == 0)
134             break;
135         s += got;
136     } while (strchr(buf, 'R') == 0);
137     _tracef("probe returned %s", _nc_visbuf(buf));
138
139     /* try to interpret as a position report */
140     if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) {
141         _tracef("position probe failed in %s", legend);
142     } else {
143         if (expected_x < 0)
144             expected_x = x - 1;
145         if (expected_y < 0)
146             expected_y = y - 1;
147         if (y - 1 != expected_y || x - 1 != expected_x) {
148             beep();
149             tputs(tparm("\033[%d;%dH", expected_y + 1, expected_x + 1), 1, _nc_outch);
150             _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
151                     y - 1, x - 1, expected_y, expected_x, legend);
152         } else {
153             _tracef("position matches OK in %s", legend);
154         }
155     }
156 }
157 #else
158 #define position_check(expected_y, expected_x, legend)  /* nothing */
159 #endif /* POSITION_DEBUG */
160
161 /****************************************************************************
162  *
163  * Optimized update code
164  *
165  ****************************************************************************/
166
167 static inline void
168 GoTo(int const row, int const col)
169 {
170     TR(TRACE_MOVE, ("GoTo(%d, %d) from (%d, %d)",
171                     row, col, SP->_cursrow, SP->_curscol));
172
173     position_check(SP->_cursrow, SP->_curscol, "GoTo");
174
175     mvcur(SP->_cursrow, SP->_curscol, row, col);
176     position_check(SP->_cursrow, SP->_curscol, "GoTo2");
177 }
178
179 static inline void
180 PutAttrChar(CARG_CH_T ch)
181 {
182     int chlen = 1;
183     NCURSES_CH_T my_ch;
184     PUTC_DATA;
185     NCURSES_CH_T tilde;
186     NCURSES_ATTR_T attr = AttrOfD(ch);
187
188     TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
189                        _tracech_t(ch),
190                        SP->_cursrow, SP->_curscol));
191 #if USE_WIDEC_SUPPORT
192     /*
193      * If this is not a valid character, there is nothing more to do.
194      */
195     if (isnac(CHDEREF(ch)))
196         return;
197     /*
198      * Determine the number of character cells which the 'ch' value will use
199      * on the screen.  It should be at least one.
200      */
201     if ((chlen = wcwidth(CharOf(CHDEREF(ch)))) <= 0) {
202         static NCURSES_CH_T blank = NewChar(BLANK_TEXT);
203
204         if (isprint(CharOf(CHDEREF(ch)))
205             || (SP->_posix_locale && CharOf(CHDEREF(ch)) >= 160)) {
206             ;
207         } else {
208             ch = CHREF(blank);
209             TR(TRACE_CHARPUT, ("forced to blank"));
210         }
211         chlen = 1;
212     }
213 #endif
214
215     if ((attr & A_ALTCHARSET)
216         && SP->_acs_map != 0
217         && CharOfD(ch) < ACS_LEN) {
218         my_ch = CHDEREF(ch);    /* work around const param */
219 #if USE_WIDEC_SUPPORT
220         /*
221          * This is crude & ugly, but works most of the time.  It checks if the
222          * acs_chars string specified that we have a mapping for this
223          * character, and uses the wide-character mapping when we expect the
224          * normal one to be broken (by mis-design ;-).
225          */
226         if (SP->_screen_acs_fix
227             && SP->_acs_map[CharOf(my_ch)] & A_ALTCHARSET) {
228             attr &= ~(A_ALTCHARSET);
229             my_ch = _nc_wacs[CharOf(my_ch)];
230         }
231 #endif
232         if (attr & A_ALTCHARSET) {
233             chtype temp = UChar(SP->_acs_map[CharOfD(ch)]);
234             if (temp != 0)
235                 SetChar(my_ch, temp, attr);
236             RemAttr(my_ch, A_ALTCHARSET);
237         }
238         ch = CHREF(my_ch);
239     }
240     if (tilde_glitch && (CharOfD(ch) == L('~'))) {
241         SetChar(tilde, L('`'), attr);
242         ch = CHREF(tilde);
243     }
244
245     UpdateAttrs(attr);
246 #if !USE_WIDEC_SUPPORT
247     /* FIXME - we do this special case for signal handling, should see how to
248      * make it work for wide characters.
249      */
250     if (SP->_outch != 0) {
251         SP->_outch(UChar(ch));
252     } else
253 #endif
254     {
255         PUTC(CHDEREF(ch), SP->_ofp);    /* macro's fastest... */
256         TRACE_OUTCHARS(1);
257     }
258     SP->_curscol += chlen;
259     if (char_padding) {
260         TPUTS_TRACE("char_padding");
261         putp(char_padding);
262     }
263 }
264
265 static bool
266 check_pending(void)
267 /* check for pending input */
268 {
269     bool have_pending = FALSE;
270
271     /*
272      * Only carry out this check when the flag is zero, otherwise we'll
273      * have the refreshing slow down drastically (or stop) if there's an
274      * unread character available.
275      */
276     if (SP->_fifohold != 0)
277         return FALSE;
278
279     if (SP->_checkfd >= 0) {
280 #if USE_FUNC_POLL
281         struct pollfd fds[1];
282         fds[0].fd = SP->_checkfd;
283         fds[0].events = POLLIN;
284         if (poll(fds, 1, 0) > 0) {
285             have_pending = TRUE;
286         }
287 #elif defined(__BEOS__)
288         /*
289          * BeOS's select() is declared in socket.h, so the configure script does
290          * not see it.  That's just as well, since that function works only for
291          * sockets.  This (using snooze and ioctl) was distilled from Be's patch
292          * for ncurses which uses a separate thread to simulate select().
293          *
294          * FIXME: the return values from the ioctl aren't very clear if we get
295          * interrupted.
296          */
297         int n = 0;
298         int howmany = ioctl(0, 'ichr', &n);
299         if (howmany >= 0 && n > 0) {
300             have_pending = TRUE;
301         }
302 #elif HAVE_SELECT
303         fd_set fdset;
304         struct timeval ktimeout;
305
306         ktimeout.tv_sec =
307             ktimeout.tv_usec = 0;
308
309         FD_ZERO(&fdset);
310         FD_SET(SP->_checkfd, &fdset);
311         if (select(SP->_checkfd + 1, &fdset, NULL, NULL, &ktimeout) != 0) {
312             have_pending = TRUE;
313         }
314 #endif
315     }
316     if (have_pending) {
317         SP->_fifohold = 5;
318         _nc_flush();
319     }
320     return FALSE;
321 }
322
323 /* put char at lower right corner */
324 static void
325 PutCharLR(const ARG_CH_T ch)
326 {
327     if (!auto_right_margin) {
328         /* we can put the char directly */
329         PutAttrChar(ch);
330     } else if (enter_am_mode && exit_am_mode) {
331         /* we can suppress automargin */
332         TPUTS_TRACE("exit_am_mode");
333         putp(exit_am_mode);
334
335         PutAttrChar(ch);
336         SP->_curscol--;
337         position_check(SP->_cursrow, SP->_curscol, "exit_am_mode");
338
339         TPUTS_TRACE("enter_am_mode");
340         putp(enter_am_mode);
341     } else if ((enter_insert_mode && exit_insert_mode)
342                || insert_character || parm_ich) {
343         GoTo(screen_lines - 1, screen_columns - 2);
344         PutAttrChar(ch);
345         GoTo(screen_lines - 1, screen_columns - 2);
346         InsStr(newscr->_line[screen_lines - 1].text + screen_columns - 2, 1);
347     }
348 }
349
350 static void
351 wrap_cursor(void)
352 {
353     if (eat_newline_glitch) {
354         /*
355          * xenl can manifest two different ways.  The vt100
356          * way is that, when you'd expect the cursor to wrap,
357          * it stays hung at the right margin (on top of the
358          * character just emitted) and doesn't wrap until the
359          * *next* graphic char is emitted.  The c100 way is
360          * to ignore LF received just after an am wrap.
361          *
362          * An aggressive way to handle this would be to
363          * emit CR/LF after the char and then assume the wrap
364          * is done, you're on the first position of the next
365          * line, and the terminal out of its weird state.
366          * Here it's safe to just tell the code that the
367          * cursor is in hyperspace and let the next mvcur()
368          * call straighten things out.
369          */
370         SP->_curscol = -1;
371         SP->_cursrow = -1;
372     } else if (auto_right_margin) {
373         SP->_curscol = 0;
374         SP->_cursrow++;
375     } else {
376         SP->_curscol--;
377     }
378     position_check(SP->_cursrow, SP->_curscol, "wrap_cursor");
379 }
380
381 static inline void
382 PutChar(const ARG_CH_T ch)
383 /* insert character, handling automargin stuff */
384 {
385     if (SP->_cursrow == screen_lines - 1 && SP->_curscol == screen_columns - 1)
386         PutCharLR(ch);
387     else
388         PutAttrChar(ch);
389
390     if (SP->_curscol >= screen_columns)
391         wrap_cursor();
392
393     position_check(SP->_cursrow, SP->_curscol, "PutChar");
394 }
395
396 /*
397  * Check whether the given character can be output by clearing commands.  This
398  * includes test for being a space and not including any 'bad' attributes, such
399  * as A_REVERSE.  All attribute flags which don't affect appearance of a space
400  * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded.
401  */
402 static inline bool
403 can_clear_with(ARG_CH_T ch)
404 {
405     if (!back_color_erase && SP->_coloron) {
406 #if NCURSES_EXT_FUNCS
407         if (!SP->_default_color)
408             return FALSE;
409         if (SP->_default_fg != C_MASK || SP->_default_bg != C_MASK)
410             return FALSE;
411         if (AttrOfD(ch) & A_COLOR) {
412             short fg, bg;
413             pair_content(PAIR_NUMBER(AttrOfD(ch)), &fg, &bg);
414             if (fg != C_MASK || bg != C_MASK)
415                 return FALSE;
416         }
417 #else
418         if (AttrOfD(ch) & A_COLOR)
419             return FALSE;
420 #endif
421     }
422     return (ISBLANK(CHDEREF(ch)) &&
423             (AttrOfD(ch) & ~(NONBLANK_ATTR | A_COLOR)) == BLANK_ATTR);
424 }
425
426 /*
427  * Issue a given span of characters from an array.
428  * Must be functionally equivalent to:
429  *      for (i = 0; i < num; i++)
430  *          PutChar(ntext[i]);
431  * but can leave the cursor positioned at the middle of the interval.
432  *
433  * Returns: 0 - cursor is at the end of interval
434  *          1 - cursor is somewhere in the middle
435  *
436  * This code is optimized using ech and rep.
437  */
438 static int
439 EmitRange(const NCURSES_CH_T * ntext, int num)
440 {
441     int i;
442
443     if (erase_chars || repeat_char) {
444         while (num > 0) {
445             int runcount;
446             NCURSES_CH_T ntext0;
447
448             while (num > 1 && !CharEq(ntext[0], ntext[1])) {
449                 PutChar(CHREF(ntext[0]));
450                 ntext++;
451                 num--;
452             }
453             ntext0 = ntext[0];
454             if (num == 1) {
455                 PutChar(CHREF(ntext0));
456                 return 0;
457             }
458             runcount = 2;
459
460             while (runcount < num && CharEq(ntext[runcount], ntext0))
461                 runcount++;
462
463             /*
464              * The cost expression in the middle isn't exactly right.
465              * _cup_ch_cost is an upper bound on the cost for moving to the
466              * end of the erased area, but not the cost itself (which we
467              * can't compute without emitting the move).  This may result
468              * in erase_chars not getting used in some situations for
469              * which it would be marginally advantageous.
470              */
471             if (erase_chars
472                 && runcount > SP->_ech_cost + SP->_cup_ch_cost
473                 && can_clear_with(CHREF(ntext0))) {
474                 UpdateAttrs(AttrOf(ntext0));
475                 putp(tparm(erase_chars, runcount));
476
477                 /*
478                  * If this is the last part of the given interval,
479                  * don't bother moving cursor, since it can be the
480                  * last update on the line.
481                  */
482                 if (runcount < num) {
483                     GoTo(SP->_cursrow, SP->_curscol + runcount);
484                 } else {
485                     return 1;   /* cursor stays in the middle */
486                 }
487             } else if (repeat_char && runcount > SP->_rep_cost) {
488                 bool wrap_possible = (SP->_curscol + runcount >= screen_columns);
489                 int rep_count = runcount;
490
491                 if (wrap_possible)
492                     rep_count--;
493
494                 UpdateAttrs(AttrOf(ntext0));
495                 tputs(tparm(repeat_char, CharOf(ntext0), rep_count),
496                       rep_count, _nc_outch);
497                 SP->_curscol += rep_count;
498
499                 if (wrap_possible)
500                     PutChar(CHREF(ntext0));
501             } else {
502                 for (i = 0; i < runcount; i++)
503                     PutChar(CHREF(ntext[i]));
504             }
505             ntext += runcount;
506             num -= runcount;
507         }
508         return 0;
509     }
510
511     for (i = 0; i < num; i++)
512         PutChar(CHREF(ntext[i]));
513     return 0;
514 }
515
516 /*
517  * Output the line in the given range [first .. last]
518  *
519  * If there's a run of identical characters that's long enough to justify
520  * cursor movement, use that also.
521  *
522  * Returns: same as EmitRange
523  */
524 static int
525 PutRange(const NCURSES_CH_T * otext,
526          const NCURSES_CH_T * ntext,
527          int row,
528          int first, int last)
529 {
530     int i, j, same;
531
532     TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)",
533                        otext, ntext, row, first, last));
534
535     if (otext != ntext
536         && (last - first + 1) > SP->_inline_cost) {
537         for (j = first, same = 0; j <= last; j++) {
538             if (!same && isnac(otext[j]))
539                 continue;
540             if (CharEq(otext[j], ntext[j])) {
541                 same++;
542             } else {
543                 if (same > SP->_inline_cost) {
544                     EmitRange(ntext + first, j - same - first);
545                     GoTo(row, first = j);
546                 }
547                 same = 0;
548             }
549         }
550         i = EmitRange(ntext + first, j - same - first);
551         /*
552          * Always return 1 for the next GoTo() after a PutRange() if we found
553          * identical characters at end of interval
554          */
555         return (same == 0 ? i : 1);
556     }
557     return EmitRange(ntext + first, last - first + 1);
558 }
559
560 /* leave unbracketed here so 'indent' works */
561 #define MARK_NOCHANGE(win,row) \
562                 win->_line[row].firstchar = _NOCHANGE; \
563                 win->_line[row].lastchar = _NOCHANGE; \
564                 if_USE_SCROLL_HINTS(win->_line[row].oldindex = row)
565
566 NCURSES_EXPORT(int)
567 doupdate(void)
568 {
569     int i;
570     int nonempty;
571 #if USE_TRACE_TIMES
572     struct tms before, after;
573 #endif /* USE_TRACE_TIMES */
574
575     T((T_CALLED("doupdate()")));
576
577 #ifdef TRACE
578     if (_nc_tracing & TRACE_UPDATE) {
579         if (curscr->_clear)
580             _tracef("curscr is clear");
581         else
582             _tracedump("curscr", curscr);
583         _tracedump("newscr", newscr);
584     }
585 #endif /* TRACE */
586
587     _nc_signal_handler(FALSE);
588
589     if (SP->_fifohold)
590         SP->_fifohold--;
591
592 #if USE_SIZECHANGE
593     if (SP->_endwin || SP->_sig_winch) {
594         /*
595          * This is a transparent extension:  XSI does not address it,
596          * and applications need not know that ncurses can do it.
597          *
598          * Check if the terminal size has changed while curses was off
599          * (this can happen in an xterm, for example), and resize the
600          * ncurses data structures accordingly.
601          */
602         _nc_update_screensize();
603     }
604 #endif
605
606     if (SP->_endwin) {
607
608         T(("coming back from shell mode"));
609         reset_prog_mode();
610
611         _nc_mvcur_resume();
612         _nc_screen_resume();
613         SP->_mouse_resume(SP);
614
615         SP->_endwin = FALSE;
616     }
617 #if USE_TRACE_TIMES
618     /* zero the metering machinery */
619     _nc_outchars = 0;
620     (void) times(&before);
621 #endif /* USE_TRACE_TIMES */
622
623     /*
624      * This is the support for magic-cookie terminals.  The
625      * theory: we scan the virtual screen looking for attribute
626      * turnons.  Where we find one, check to make sure it's
627      * realizable by seeing if the required number of
628      * un-attributed blanks are present before and after the
629      * attributed range; try to shift the range boundaries over
630      * blanks (not changing the screen display) so this becomes
631      * true.  If it is, shift the beginning attribute change
632      * appropriately (the end one, if we've gotten this far, is
633      * guaranteed room for its cookie). If not, nuke the added
634      * attributes out of the span.
635      */
636 #if USE_XMC_SUPPORT
637     if (magic_cookie_glitch > 0) {
638         int j, k;
639         attr_t rattr = A_NORMAL;
640
641         for (i = 0; i < screen_lines; i++) {
642             for (j = 0; j < screen_columns; j++) {
643                 bool failed = FALSE;
644                 attr_t turnon = AttrOf(newscr->_line[i].text[j]) & ~rattr;
645
646                 /* is an attribute turned on here? */
647                 if (turnon == 0) {
648                     rattr = AttrOf(newscr->_line[i].text[j]);
649                     continue;
650                 }
651
652                 TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
653                 TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon)));
654
655                 /*
656                  * If the attribute change location is a blank with a
657                  * "safe" attribute, undo the attribute turnon.  This may
658                  * ensure there's enough room to set the attribute before
659                  * the first non-blank in the run.
660                  */
661 #define SAFE(a) (!((a) & (attr_t)~NONBLANK_ATTR))
662                 if (ISBLANK(newscr->_line[i].text[j]) && SAFE(turnon)) {
663                     RemAttr(newscr->_line[i].text[j], turnon);
664                     continue;
665                 }
666
667                 /* check that there's enough room at start of span */
668                 for (k = 1; k <= magic_cookie_glitch; k++) {
669                     if (j - k < 0
670                         || !ISBLANK(newscr->_line[i].text[j - k])
671                         || !SAFE(AttrOf(newscr->_line[i].text[j - k])))
672                         failed = TRUE;
673                 }
674                 if (!failed) {
675                     bool end_onscreen = FALSE;
676                     int m, n = j;
677
678                     /* find end of span, if it's onscreen */
679                     for (m = i; m < screen_lines; m++) {
680                         for (; n < screen_columns; n++) {
681                             if (AttrOf(newscr->_line[m].text[n]) == rattr) {
682                                 end_onscreen = TRUE;
683                                 TR(TRACE_ATTRS,
684                                    ("Range attributed with %s ends at (%d, %d)",
685                                     _traceattr(turnon), m, n));
686                                 goto foundit;
687                             }
688                         }
689                         n = 0;
690                     }
691                     TR(TRACE_ATTRS,
692                        ("Range attributed with %s ends offscreen",
693                         _traceattr(turnon)));
694                   foundit:;
695
696                     if (end_onscreen) {
697                         NCURSES_CH_T *lastline = newscr->_line[m].text;
698
699                         /*
700                          * If there are safely-attributed blanks at the
701                          * end of the range, shorten the range.  This will
702                          * help ensure that there is enough room at end
703                          * of span.
704                          */
705                         while (n >= 0
706                                && ISBLANK(lastline[n])
707                                && SAFE(AttrOf(lastline[n])))
708                             RemAttr(lastline[n--], turnon);
709
710                         /* check that there's enough room at end of span */
711                         for (k = 1; k <= magic_cookie_glitch; k++)
712                             if (n + k >= screen_columns
713                                 || !ISBLANK(lastline[n + k])
714                                 || !SAFE(AttrOf(lastline[n + k])))
715                                 failed = TRUE;
716                     }
717                 }
718
719                 if (failed) {
720                     int p, q = j;
721
722                     TR(TRACE_ATTRS,
723                        ("Clearing %s beginning at (%d, %d)",
724                         _traceattr(turnon), i, j));
725
726                     /* turn off new attributes over span */
727                     for (p = i; p < screen_lines; p++) {
728                         for (; q < screen_columns; q++) {
729                             if (AttrOf(newscr->_line[p].text[q]) == rattr)
730                                 goto foundend;
731                             RemAttr(newscr->_line[p].text[q], turnon);
732                         }
733                         q = 0;
734                     }
735                   foundend:;
736                 } else {
737                     TR(TRACE_ATTRS,
738                        ("Cookie space for %s found before (%d, %d)",
739                         _traceattr(turnon), i, j));
740
741                     /*
742                      * back up the start of range so there's room
743                      * for cookies before the first nonblank character
744                      */
745                     for (k = 1; k <= magic_cookie_glitch; k++)
746                         AddAttr(newscr->_line[i].text[j - k], turnon);
747                 }
748
749                 rattr = AttrOf(newscr->_line[i].text[j]);
750             }
751         }
752
753 #ifdef TRACE
754         /* show altered highlights after magic-cookie check */
755         if (_nc_tracing & TRACE_UPDATE) {
756             _tracef("After magic-cookie check...");
757             _tracedump("newscr", newscr);
758         }
759 #endif /* TRACE */
760     }
761 #endif /* USE_XMC_SUPPORT */
762
763     nonempty = 0;
764     if (curscr->_clear || newscr->_clear) {     /* force refresh ? */
765         TR(TRACE_UPDATE, ("clearing and updating from scratch"));
766         ClrUpdate();
767         curscr->_clear = FALSE; /* reset flag */
768         newscr->_clear = FALSE; /* reset flag */
769     } else {
770         int changedlines = CHECK_INTERVAL;
771
772         if (check_pending())
773             goto cleanup;
774
775         nonempty = min(screen_lines, newscr->_maxy + 1);
776
777         if (SP->_scrolling) {
778             _nc_scroll_optimize();
779         }
780
781         nonempty = ClrBottom(nonempty);
782
783         TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty));
784         for (i = 0; i < nonempty; i++) {
785             /*
786              * Here is our line-breakout optimization.
787              */
788             if (changedlines == CHECK_INTERVAL) {
789                 if (check_pending())
790                     goto cleanup;
791                 changedlines = 0;
792             }
793
794             /*
795              * newscr->line[i].firstchar is normally set
796              * by wnoutrefresh.  curscr->line[i].firstchar
797              * is normally set by _nc_scroll_window in the
798              * vertical-movement optimization code,
799              */
800             if (newscr->_line[i].firstchar != _NOCHANGE
801                 || curscr->_line[i].firstchar != _NOCHANGE) {
802                 TransformLine(i);
803                 changedlines++;
804             }
805
806             /* mark line changed successfully */
807             if (i <= newscr->_maxy) {
808                 MARK_NOCHANGE(newscr, i);
809             }
810             if (i <= curscr->_maxy) {
811                 MARK_NOCHANGE(curscr, i);
812             }
813         }
814     }
815
816     /* put everything back in sync */
817     for (i = nonempty; i <= newscr->_maxy; i++) {
818         MARK_NOCHANGE(newscr, i);
819     }
820     for (i = nonempty; i <= curscr->_maxy; i++) {
821         MARK_NOCHANGE(curscr, i);
822     }
823
824     if (!newscr->_leaveok) {
825         curscr->_curx = newscr->_curx;
826         curscr->_cury = newscr->_cury;
827
828         GoTo(curscr->_cury, curscr->_curx);
829     }
830
831   cleanup:
832     /*
833      * Keep the physical screen in normal mode in case we get other
834      * processes writing to the screen.
835      */
836     UpdateAttrs(A_NORMAL);
837
838     _nc_flush();
839     curscr->_attrs = newscr->_attrs;
840
841 #if USE_TRACE_TIMES
842     (void) times(&after);
843     TR(TRACE_TIMES,
844        ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
845         _nc_outchars,
846         (long) (after.tms_stime - before.tms_stime),
847         (long) (after.tms_utime - before.tms_utime)));
848 #endif /* USE_TRACE_TIMES */
849
850     _nc_signal_handler(TRUE);
851
852     returnCode(OK);
853 }
854
855 /*
856  *      ClrBlank(win)
857  *
858  *      Returns the attributed character that corresponds to the "cleared"
859  *      screen.  If the terminal has the back-color-erase feature, this will be
860  *      colored according to the wbkgd() call.
861  *
862  *      We treat 'curscr' specially because it isn't supposed to be set directly
863  *      in the wbkgd() call.  Assume 'stdscr' for this case.
864  */
865 #define BCE_ATTRS (A_NORMAL|A_COLOR)
866 #define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_nc_bkgd)
867
868 static inline NCURSES_CH_T
869 ClrBlank(WINDOW *win)
870 {
871     NCURSES_CH_T blank = NewChar(BLANK_TEXT);
872     if (back_color_erase)
873         AddAttr(blank, (AttrOf(BCE_BKGD(win)) & BCE_ATTRS));
874     return blank;
875 }
876
877 /*
878 **      ClrUpdate()
879 **
880 **      Update by clearing and redrawing the entire screen.
881 **
882 */
883
884 static void
885 ClrUpdate(void)
886 {
887     int i;
888     NCURSES_CH_T blank = ClrBlank(stdscr);
889     int nonempty = min(screen_lines, newscr->_maxy + 1);
890
891     TR(TRACE_UPDATE, ("ClrUpdate() called"));
892
893     ClearScreen(blank);
894
895     TR(TRACE_UPDATE, ("updating screen from scratch"));
896
897     nonempty = ClrBottom(nonempty);
898
899     for (i = 0; i < nonempty; i++)
900         TransformLine(i);
901 }
902
903 /*
904 **      ClrToEOL(blank)
905 **
906 **      Clear to end of current line, starting at the cursor position
907 */
908
909 static void
910 ClrToEOL(NCURSES_CH_T blank, bool needclear)
911 {
912     int j;
913
914     if (curscr != 0
915         && SP->_cursrow >= 0) {
916         for (j = SP->_curscol; j < screen_columns; j++) {
917             if (j >= 0) {
918                 NCURSES_CH_T *cp = &(curscr->_line[SP->_cursrow].text[j]);
919
920                 if (!CharEq(*cp, blank)) {
921                     *cp = blank;
922                     needclear = TRUE;
923                 }
924             }
925         }
926     } else {
927         needclear = TRUE;
928     }
929
930     if (needclear) {
931         UpdateAttrs(AttrOf(blank));
932         TPUTS_TRACE("clr_eol");
933         if (clr_eol && SP->_el_cost <= (screen_columns - SP->_curscol)) {
934             putp(clr_eol);
935         } else {
936             int count = (screen_columns - SP->_curscol);
937             while (count-- > 0)
938                 PutChar(CHREF(blank));
939         }
940     }
941 }
942
943 /*
944 **      ClrToEOS(blank)
945 **
946 **      Clear to end of screen, starting at the cursor position
947 */
948
949 static void
950 ClrToEOS(NCURSES_CH_T blank)
951 {
952     int row, col;
953
954     row = SP->_cursrow;
955     col = SP->_curscol;
956
957     UpdateAttrs(AttrOf(blank));
958     TPUTS_TRACE("clr_eos");
959     tputs(clr_eos, screen_lines - row, _nc_outch);
960
961     while (col < screen_columns)
962         curscr->_line[row].text[col++] = blank;
963
964     for (row++; row < screen_lines; row++) {
965         for (col = 0; col < screen_columns; col++)
966             curscr->_line[row].text[col] = blank;
967     }
968 }
969
970 /*
971  *      ClrBottom(total)
972  *
973  *      Test if clearing the end of the screen would satisfy part of the
974  *      screen-update.  Do this by scanning backwards through the lines in the
975  *      screen, checking if each is blank, and one or more are changed.
976  */
977 static int
978 ClrBottom(int total)
979 {
980     int row;
981     int col;
982     int top = total;
983     int last = min(screen_columns, newscr->_maxx + 1);
984     NCURSES_CH_T blank = newscr->_line[total - 1].text[last - 1];
985     bool ok;
986
987     if (clr_eos && can_clear_with(CHREF(blank))) {
988
989         for (row = total - 1; row >= 0; row--) {
990             for (col = 0, ok = TRUE; ok && col < last; col++) {
991                 ok = (CharEq(newscr->_line[row].text[col], blank));
992             }
993             if (!ok)
994                 break;
995
996             for (col = 0; ok && col < last; col++) {
997                 ok = (CharEq(curscr->_line[row].text[col], blank));
998             }
999             if (!ok)
1000                 top = row;
1001         }
1002
1003         /* don't use clr_eos for just one line if clr_eol available */
1004         if (top < total) {
1005             GoTo(top, 0);
1006             ClrToEOS(blank);
1007             if (SP->oldhash && SP->newhash) {
1008                 for (row = top; row < screen_lines; row++)
1009                     SP->oldhash[row] = SP->newhash[row];
1010             }
1011         }
1012     }
1013     return top;
1014 }
1015
1016 #if USE_XMC_SUPPORT
1017 #if USE_WIDEC_SUPPORT
1018 static inline bool
1019 check_xmc_transition(NCURSES_CH_T * a, NCURSES_CH_T * b)
1020 {
1021     if (((a->attr ^ b->attr) & ~(a->attr) & SP->_xmc_triggers) != 0) {
1022         return TRUE;
1023     }
1024     return FALSE;
1025 }
1026 #define xmc_turn_on(a,b) check_xmc_transition(&(a), &(b))
1027 #else
1028 #define xmc_turn_on(a,b) ((((a)^(b)) & ~(a) & SP->_xmc_triggers) != 0)
1029 #endif
1030
1031 #define xmc_new(r,c) newscr->_line[r].text[c]
1032 #define xmc_turn_off(a,b) xmc_turn_on(b,a)
1033 #endif /* USE_XMC_SUPPORT */
1034
1035 /*
1036 **      TransformLine(lineno)
1037 **
1038 **      Transform the given line in curscr to the one in newscr, using
1039 **      Insert/Delete Character if _nc_idcok && has_ic().
1040 **
1041 **              firstChar = position of first different character in line
1042 **              oLastChar = position of last different character in old line
1043 **              nLastChar = position of last different character in new line
1044 **
1045 **              move to firstChar
1046 **              overwrite chars up to min(oLastChar, nLastChar)
1047 **              if oLastChar < nLastChar
1048 **                      insert newLine[oLastChar+1..nLastChar]
1049 **              else
1050 **                      delete oLastChar - nLastChar spaces
1051 */
1052
1053 static void
1054 TransformLine(int const lineno)
1055 {
1056     int firstChar, oLastChar, nLastChar;
1057     NCURSES_CH_T *newLine = newscr->_line[lineno].text;
1058     NCURSES_CH_T *oldLine = curscr->_line[lineno].text;
1059     int n;
1060     bool attrchanged = FALSE;
1061
1062     TR(TRACE_UPDATE, ("TransformLine(%d) called", lineno));
1063
1064     /* copy new hash value to old one */
1065     if (SP->oldhash && SP->newhash)
1066         SP->oldhash[lineno] = SP->newhash[lineno];
1067
1068 #define ColorOf(n) (AttrOf(n) & A_COLOR)
1069 #define unColor(n) (AttrOf(n) & ALL_BUT_COLOR)
1070     /*
1071      * If we have colors, there is the possibility of having two color pairs
1072      * that display as the same colors.  For instance, Lynx does this.  Check
1073      * for this case, and update the old line with the new line's colors when
1074      * they are equivalent.
1075      */
1076     if (SP->_coloron) {
1077         attr_t oldColor;
1078         attr_t newColor;
1079         int oldPair;
1080         int newPair;
1081
1082         for (n = 0; n < screen_columns; n++) {
1083             if (!CharEq(newLine[n], oldLine[n])) {
1084                 oldColor = ColorOf(oldLine[n]);
1085                 newColor = ColorOf(newLine[n]);
1086                 if (oldColor != newColor
1087                     && unColor(oldLine[n]) == unColor(newLine[n])) {
1088                     oldPair = PAIR_NUMBER(oldColor);
1089                     newPair = PAIR_NUMBER(newColor);
1090                     if (oldPair < COLOR_PAIRS
1091                         && newPair < COLOR_PAIRS
1092                         && SP->_color_pairs[oldPair] == SP->_color_pairs[newPair]) {
1093                         RemAttr(oldLine[n], A_COLOR);
1094                         AddAttr(oldLine[n], ColorOf(newLine[n]));
1095                     }
1096                 }
1097             }
1098         }
1099     }
1100
1101     if (ceol_standout_glitch && clr_eol) {
1102         firstChar = 0;
1103         while (firstChar < screen_columns) {
1104             if (AttrOf(newLine[firstChar]) != AttrOf(oldLine[firstChar])) {
1105                 attrchanged = TRUE;
1106                 break;
1107             }
1108             firstChar++;
1109         }
1110     }
1111
1112     firstChar = 0;
1113
1114     if (attrchanged) {          /* we may have to disregard the whole line */
1115         GoTo(lineno, firstChar);
1116         ClrToEOL(ClrBlank(curscr), FALSE);
1117         PutRange(oldLine, newLine, lineno, 0, (screen_columns - 1));
1118 #if USE_XMC_SUPPORT
1119
1120         /*
1121          * This is a very simple loop to paint characters which may have the
1122          * magic cookie glitch embedded.  It doesn't know much about video
1123          * attributes which are continued from one line to the next.  It
1124          * assumes that we have filtered out requests for attribute changes
1125          * that do not get mapped to blank positions.
1126          *
1127          * FIXME: we are not keeping track of where we put the cookies, so this
1128          * will work properly only once, since we may overwrite a cookie in a
1129          * following operation.
1130          */
1131     } else if (magic_cookie_glitch > 0) {
1132         GoTo(lineno, firstChar);
1133         for (n = 0; n < screen_columns; n++) {
1134             int m = n + magic_cookie_glitch;
1135
1136             /* check for turn-on:
1137              * If we are writing an attributed blank, where the
1138              * previous cell is not attributed.
1139              */
1140             if (ISBLANK(newLine[n])
1141                 && ((n > 0
1142                      && xmc_turn_on(newLine[n - 1], newLine[n]))
1143                     || (n == 0
1144                         && lineno > 0
1145                         && xmc_turn_on(xmc_new(lineno - 1, screen_columns - 1),
1146                                        newLine[n])))) {
1147                 n = m;
1148             }
1149
1150             PutChar(CHREF(newLine[n]));
1151
1152             /* check for turn-off:
1153              * If we are writing an attributed non-blank, where the
1154              * next cell is blank, and not attributed.
1155              */
1156             if (!ISBLANK(newLine[n])
1157                 && ((n + 1 < screen_columns
1158                      && xmc_turn_off(newLine[n], newLine[n + 1]))
1159                     || (n + 1 >= screen_columns
1160                         && lineno + 1 < screen_lines
1161                         && xmc_turn_off(newLine[n], xmc_new(lineno + 1, 0))))) {
1162                 n = m;
1163             }
1164
1165         }
1166 #endif
1167     } else {
1168         NCURSES_CH_T blank;
1169
1170         /* it may be cheap to clear leading whitespace with clr_bol */
1171         blank = newLine[0];
1172         if (clr_bol && can_clear_with(CHREF(blank))) {
1173             int oFirstChar, nFirstChar;
1174
1175             for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++)
1176                 if (!CharEq(oldLine[oFirstChar], blank))
1177                     break;
1178             for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++)
1179                 if (!CharEq(newLine[nFirstChar], blank))
1180                     break;
1181
1182             if (nFirstChar == oFirstChar) {
1183                 firstChar = nFirstChar;
1184                 /* find the first differing character */
1185                 while (firstChar < screen_columns
1186                        && CharEq(newLine[firstChar], oldLine[firstChar]))
1187                     firstChar++;
1188             } else if (oFirstChar > nFirstChar) {
1189                 firstChar = nFirstChar;
1190             } else {            /* oFirstChar < nFirstChar */
1191                 firstChar = oFirstChar;
1192                 if (SP->_el1_cost < nFirstChar - oFirstChar) {
1193                     if (nFirstChar >= screen_columns
1194                         && SP->_el_cost <= SP->_el1_cost) {
1195                         GoTo(lineno, 0);
1196                         UpdateAttrs(AttrOf(blank));
1197                         TPUTS_TRACE("clr_eol");
1198                         putp(clr_eol);
1199                     } else {
1200                         GoTo(lineno, nFirstChar - 1);
1201                         UpdateAttrs(AttrOf(blank));
1202                         TPUTS_TRACE("clr_bol");
1203                         putp(clr_bol);
1204                     }
1205
1206                     while (firstChar < nFirstChar)
1207                         oldLine[firstChar++] = blank;
1208                 }
1209             }
1210         } else {
1211             /* find the first differing character */
1212             while (firstChar < screen_columns
1213                    && CharEq(newLine[firstChar], oldLine[firstChar]))
1214                 firstChar++;
1215         }
1216         /* if there wasn't one, we're done */
1217         if (firstChar >= screen_columns)
1218             return;
1219
1220         blank = newLine[screen_columns - 1];
1221
1222         if (!can_clear_with(CHREF(blank))) {
1223             /* find the last differing character */
1224             nLastChar = screen_columns - 1;
1225
1226             while (nLastChar > firstChar
1227                    && CharEq(newLine[nLastChar], oldLine[nLastChar]))
1228                 nLastChar--;
1229
1230             if (nLastChar >= firstChar) {
1231                 GoTo(lineno, firstChar);
1232                 PutRange(oldLine, newLine, lineno, firstChar, nLastChar);
1233                 memcpy(oldLine + firstChar,
1234                        newLine + firstChar,
1235                        (nLastChar - firstChar + 1) * sizeof(NCURSES_CH_T));
1236             }
1237             return;
1238         }
1239
1240         /* find last non-blank character on old line */
1241         oLastChar = screen_columns - 1;
1242         while (oLastChar > firstChar && CharEq(oldLine[oLastChar], blank))
1243             oLastChar--;
1244
1245         /* find last non-blank character on new line */
1246         nLastChar = screen_columns - 1;
1247         while (nLastChar > firstChar && CharEq(newLine[nLastChar], blank))
1248             nLastChar--;
1249
1250         if ((nLastChar == firstChar)
1251             && (SP->_el_cost < (oLastChar - nLastChar))) {
1252             GoTo(lineno, firstChar);
1253             if (!CharEq(newLine[firstChar], blank))
1254                 PutChar(CHREF(newLine[firstChar]));
1255             ClrToEOL(blank, FALSE);
1256         } else if ((nLastChar != oLastChar)
1257                    && (!CharEq(newLine[nLastChar], oldLine[oLastChar])
1258                        || !(_nc_idcok && has_ic()))) {
1259             GoTo(lineno, firstChar);
1260             if ((oLastChar - nLastChar) > SP->_el_cost) {
1261                 if (PutRange(oldLine, newLine, lineno, firstChar, nLastChar))
1262                     GoTo(lineno, nLastChar + 1);
1263                 ClrToEOL(blank, FALSE);
1264             } else {
1265                 n = max(nLastChar, oLastChar);
1266                 PutRange(oldLine, newLine, lineno, firstChar, n);
1267             }
1268         } else {
1269             int nLastNonblank = nLastChar;
1270             int oLastNonblank = oLastChar;
1271
1272             /* find the last characters that really differ */
1273             /* can be -1 if no characters differ */
1274             while (CharEq(newLine[nLastChar], oldLine[oLastChar])) {
1275                 /* don't split a wide char */
1276                 if (isnac(newLine[nLastChar]) &&
1277                     !CharEq(newLine[nLastChar - 1], oldLine[oLastChar - 1]))
1278                     break;
1279                 nLastChar--;
1280                 oLastChar--;
1281                 if (nLastChar == -1 || oLastChar == -1)
1282                     break;
1283             }
1284
1285             n = min(oLastChar, nLastChar);
1286             if (n >= firstChar) {
1287                 GoTo(lineno, firstChar);
1288                 PutRange(oldLine, newLine, lineno, firstChar, n);
1289             }
1290
1291             if (oLastChar < nLastChar) {
1292                 int m = max(nLastNonblank, oLastNonblank);
1293                 GoTo(lineno, n + 1);
1294                 if (InsCharCost(nLastChar - oLastChar)
1295                     > (m - n)) {
1296                     PutRange(oldLine, newLine, lineno, n + 1, m);
1297                 } else {
1298                     InsStr(&newLine[n + 1], nLastChar - oLastChar);
1299                 }
1300             } else if (oLastChar > nLastChar) {
1301                 GoTo(lineno, n + 1);
1302                 if (DelCharCost(oLastChar - nLastChar)
1303                     > SP->_el_cost + nLastNonblank - (n + 1)) {
1304                     if (PutRange(oldLine, newLine, lineno,
1305                                  n + 1, nLastNonblank))
1306                         GoTo(lineno, nLastNonblank + 1);
1307                     ClrToEOL(blank, FALSE);
1308                 } else {
1309                     /*
1310                      * The delete-char sequence will
1311                      * effectively shift in blanks from the
1312                      * right margin of the screen.  Ensure
1313                      * that they are the right color by
1314                      * setting the video attributes from
1315                      * the last character on the row.
1316                      */
1317                     UpdateAttrs(AttrOf(blank));
1318                     DelChar(oLastChar - nLastChar);
1319                 }
1320             }
1321         }
1322     }
1323
1324     /* update the code's internal representation */
1325     if (screen_columns > firstChar)
1326         memcpy(oldLine + firstChar,
1327                newLine + firstChar,
1328                (screen_columns - firstChar) * sizeof(NCURSES_CH_T));
1329 }
1330
1331 /*
1332 **      ClearScreen(blank)
1333 **
1334 **      Clear the physical screen and put cursor at home
1335 **
1336 */
1337
1338 static void
1339 ClearScreen(NCURSES_CH_T blank)
1340 {
1341     int i, j;
1342     bool fast_clear = (clear_screen || clr_eos || clr_eol);
1343
1344     TR(TRACE_UPDATE, ("ClearScreen() called"));
1345
1346 #if NCURSES_EXT_FUNCS
1347     if (SP->_coloron
1348         && !SP->_default_color) {
1349         _nc_do_color((int) COLOR_PAIR(SP->_current_attr), 0, FALSE, _nc_outch);
1350         if (!back_color_erase) {
1351             fast_clear = FALSE;
1352         }
1353     }
1354 #endif
1355
1356     if (fast_clear) {
1357         if (clear_screen) {
1358             UpdateAttrs(AttrOf(blank));
1359             TPUTS_TRACE("clear_screen");
1360             putp(clear_screen);
1361             SP->_cursrow = SP->_curscol = 0;
1362             position_check(SP->_cursrow, SP->_curscol, "ClearScreen");
1363         } else if (clr_eos) {
1364             SP->_cursrow = SP->_curscol = -1;
1365             GoTo(0, 0);
1366
1367             UpdateAttrs(AttrOf(blank));
1368             TPUTS_TRACE("clr_eos");
1369             tputs(clr_eos, screen_lines, _nc_outch);
1370         } else if (clr_eol) {
1371             SP->_cursrow = SP->_curscol = -1;
1372
1373             UpdateAttrs(AttrOf(blank));
1374             for (i = 0; i < screen_lines; i++) {
1375                 GoTo(i, 0);
1376                 TPUTS_TRACE("clr_eol");
1377                 putp(clr_eol);
1378             }
1379             GoTo(0, 0);
1380         }
1381     } else {
1382         UpdateAttrs(AttrOf(blank));
1383         for (i = 0; i < screen_lines; i++) {
1384             GoTo(i, 0);
1385             for (j = 0; j < screen_columns; j++)
1386                 PutChar(CHREF(blank));
1387         }
1388         GoTo(0, 0);
1389     }
1390
1391     for (i = 0; i < screen_lines; i++) {
1392         for (j = 0; j < screen_columns; j++)
1393             curscr->_line[i].text[j] = blank;
1394     }
1395
1396     TR(TRACE_UPDATE, ("screen cleared"));
1397 }
1398
1399 /*
1400 **      InsStr(line, count)
1401 **
1402 **      Insert the count characters pointed to by line.
1403 **
1404 */
1405
1406 static void
1407 InsStr(NCURSES_CH_T * line, int count)
1408 {
1409     TR(TRACE_UPDATE, ("InsStr(%p,%d) called", line, count));
1410
1411     /* Prefer parm_ich as it has the smallest cost - no need to shift
1412      * the whole line on each character. */
1413     /* The order must match that of InsCharCost. */
1414     if (parm_ich) {
1415         TPUTS_TRACE("parm_ich");
1416         tputs(tparm(parm_ich, count), count, _nc_outch);
1417         while (count) {
1418             PutAttrChar(CHREF(*line));
1419             line++;
1420             count--;
1421         }
1422     } else if (enter_insert_mode && exit_insert_mode) {
1423         TPUTS_TRACE("enter_insert_mode");
1424         putp(enter_insert_mode);
1425         while (count) {
1426             PutAttrChar(CHREF(*line));
1427             if (insert_padding) {
1428                 TPUTS_TRACE("insert_padding");
1429                 putp(insert_padding);
1430             }
1431             line++;
1432             count--;
1433         }
1434         TPUTS_TRACE("exit_insert_mode");
1435         putp(exit_insert_mode);
1436     } else {
1437         while (count) {
1438             TPUTS_TRACE("insert_character");
1439             putp(insert_character);
1440             PutAttrChar(CHREF(*line));
1441             if (insert_padding) {
1442                 TPUTS_TRACE("insert_padding");
1443                 putp(insert_padding);
1444             }
1445             line++;
1446             count--;
1447         }
1448     }
1449     position_check(SP->_cursrow, SP->_curscol, "InsStr");
1450 }
1451
1452 /*
1453 **      DelChar(count)
1454 **
1455 **      Delete count characters at current position
1456 **
1457 */
1458
1459 static void
1460 DelChar(int count)
1461 {
1462     int n;
1463
1464     TR(TRACE_UPDATE, ("DelChar(%d) called, position = (%d,%d)", count,
1465                       newscr->_cury, newscr->_curx));
1466
1467     if (parm_dch) {
1468         TPUTS_TRACE("parm_dch");
1469         tputs(tparm(parm_dch, count), count, _nc_outch);
1470     } else {
1471         for (n = 0; n < count; n++) {
1472             TPUTS_TRACE("delete_character");
1473             putp(delete_character);
1474         }
1475     }
1476 }
1477
1478 /*
1479  * Physical-scrolling support
1480  *
1481  * This code was adapted from Keith Bostic's hardware scrolling
1482  * support for 4.4BSD curses.  I (esr) translated it to use terminfo
1483  * capabilities, narrowed the call interface slightly, and cleaned
1484  * up some convoluted tests.  I also added support for the memory_above
1485  * memory_below, and non_dest_scroll_region capabilities.
1486  *
1487  * For this code to work, we must have either
1488  * change_scroll_region and scroll forward/reverse commands, or
1489  * insert and delete line capabilities.
1490  * When the scrolling region has been set, the cursor has to
1491  * be at the last line of the region to make the scroll up
1492  * happen, or on the first line of region to scroll down.
1493  *
1494  * This code makes one aesthetic decision in the opposite way from
1495  * BSD curses.  BSD curses preferred pairs of il/dl operations
1496  * over scrolls, allegedly because il/dl looked faster.  We, on
1497  * the other hand, prefer scrolls because (a) they're just as fast
1498  * on many terminals and (b) using them avoids bouncing an
1499  * unchanged bottom section of the screen up and down, which is
1500  * visually nasty.
1501  *
1502  * (lav): added more cases, used dl/il when bot==maxy and in csr case.
1503  *
1504  * I used assumption that capabilities il/il1/dl/dl1 work inside
1505  * changed scroll region not shifting screen contents outside of it.
1506  * If there are any terminals behaving different way, it would be
1507  * necessary to add some conditions to scroll_csr_forward/backward.
1508  */
1509
1510 /* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */
1511 static int
1512 scroll_csr_forward(int n, int top, int bot, int miny, int maxy, NCURSES_CH_T blank)
1513 {
1514     int i;
1515
1516     if (n == 1 && scroll_forward && top == miny && bot == maxy) {
1517         GoTo(bot, 0);
1518         UpdateAttrs(AttrOf(blank));
1519         TPUTS_TRACE("scroll_forward");
1520         putp(scroll_forward);
1521     } else if (n == 1 && delete_line && bot == maxy) {
1522         GoTo(top, 0);
1523         UpdateAttrs(AttrOf(blank));
1524         TPUTS_TRACE("delete_line");
1525         putp(delete_line);
1526     } else if (parm_index && top == miny && bot == maxy) {
1527         GoTo(bot, 0);
1528         UpdateAttrs(AttrOf(blank));
1529         TPUTS_TRACE("parm_index");
1530         tputs(tparm(parm_index, n, 0), n, _nc_outch);
1531     } else if (parm_delete_line && bot == maxy) {
1532         GoTo(top, 0);
1533         UpdateAttrs(AttrOf(blank));
1534         TPUTS_TRACE("parm_delete_line");
1535         tputs(tparm(parm_delete_line, n, 0), n, _nc_outch);
1536     } else if (scroll_forward && top == miny && bot == maxy) {
1537         GoTo(bot, 0);
1538         UpdateAttrs(AttrOf(blank));
1539         for (i = 0; i < n; i++) {
1540             TPUTS_TRACE("scroll_forward");
1541             putp(scroll_forward);
1542         }
1543     } else if (delete_line && bot == maxy) {
1544         GoTo(top, 0);
1545         UpdateAttrs(AttrOf(blank));
1546         for (i = 0; i < n; i++) {
1547             TPUTS_TRACE("delete_line");
1548             putp(delete_line);
1549         }
1550     } else
1551         return ERR;
1552
1553 #if NCURSES_EXT_FUNCS
1554     if (FILL_BCE()) {
1555         int j;
1556         for (i = 0; i < n; i++) {
1557             GoTo(bot - i, 0);
1558             for (j = 0; j < screen_columns; j++)
1559                 PutChar(CHREF(blank));
1560         }
1561     }
1562 #endif
1563     return OK;
1564 }
1565
1566 /* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */
1567 /* n > 0 */
1568 static int
1569 scroll_csr_backward(int n, int top, int bot, int miny, int maxy,
1570                     NCURSES_CH_T blank)
1571 {
1572     int i;
1573
1574     if (n == 1 && scroll_reverse && top == miny && bot == maxy) {
1575         GoTo(top, 0);
1576         UpdateAttrs(AttrOf(blank));
1577         TPUTS_TRACE("scroll_reverse");
1578         putp(scroll_reverse);
1579     } else if (n == 1 && insert_line && bot == maxy) {
1580         GoTo(top, 0);
1581         UpdateAttrs(AttrOf(blank));
1582         TPUTS_TRACE("insert_line");
1583         putp(insert_line);
1584     } else if (parm_rindex && top == miny && bot == maxy) {
1585         GoTo(top, 0);
1586         UpdateAttrs(AttrOf(blank));
1587         TPUTS_TRACE("parm_rindex");
1588         tputs(tparm(parm_rindex, n, 0), n, _nc_outch);
1589     } else if (parm_insert_line && bot == maxy) {
1590         GoTo(top, 0);
1591         UpdateAttrs(AttrOf(blank));
1592         TPUTS_TRACE("parm_insert_line");
1593         tputs(tparm(parm_insert_line, n, 0), n, _nc_outch);
1594     } else if (scroll_reverse && top == miny && bot == maxy) {
1595         GoTo(top, 0);
1596         UpdateAttrs(AttrOf(blank));
1597         for (i = 0; i < n; i++) {
1598             TPUTS_TRACE("scroll_reverse");
1599             putp(scroll_reverse);
1600         }
1601     } else if (insert_line && bot == maxy) {
1602         GoTo(top, 0);
1603         UpdateAttrs(AttrOf(blank));
1604         for (i = 0; i < n; i++) {
1605             TPUTS_TRACE("insert_line");
1606             putp(insert_line);
1607         }
1608     } else
1609         return ERR;
1610
1611 #if NCURSES_EXT_FUNCS
1612     if (FILL_BCE()) {
1613         int j;
1614         for (i = 0; i < n; i++) {
1615             GoTo(top + i, 0);
1616             for (j = 0; j < screen_columns; j++)
1617                 PutChar(CHREF(blank));
1618         }
1619     }
1620 #endif
1621     return OK;
1622 }
1623
1624 /* scroll by using delete_line at del and insert_line at ins */
1625 /* n > 0 */
1626 static int
1627 scroll_idl(int n, int del, int ins, NCURSES_CH_T blank)
1628 {
1629     int i;
1630
1631     if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line)))
1632         return ERR;
1633
1634     GoTo(del, 0);
1635     UpdateAttrs(AttrOf(blank));
1636     if (n == 1 && delete_line) {
1637         TPUTS_TRACE("delete_line");
1638         putp(delete_line);
1639     } else if (parm_delete_line) {
1640         TPUTS_TRACE("parm_delete_line");
1641         tputs(tparm(parm_delete_line, n, 0), n, _nc_outch);
1642     } else {                    /* if (delete_line) */
1643         for (i = 0; i < n; i++) {
1644             TPUTS_TRACE("delete_line");
1645             putp(delete_line);
1646         }
1647     }
1648
1649     GoTo(ins, 0);
1650     UpdateAttrs(AttrOf(blank));
1651     if (n == 1 && insert_line) {
1652         TPUTS_TRACE("insert_line");
1653         putp(insert_line);
1654     } else if (parm_insert_line) {
1655         TPUTS_TRACE("parm_insert_line");
1656         tputs(tparm(parm_insert_line, n, 0), n, _nc_outch);
1657     } else {                    /* if (insert_line) */
1658         for (i = 0; i < n; i++) {
1659             TPUTS_TRACE("insert_line");
1660             putp(insert_line);
1661         }
1662     }
1663
1664     return OK;
1665 }
1666
1667 /*
1668  * Note:  some terminals require the cursor to be within the scrolling margins
1669  * before setting them.  Generally, the cursor must be at the appropriate end
1670  * of the scrolling margins when issuing an indexing operation (it is not
1671  * apparent whether it must also be at the left margin; we do this just to be
1672  * safe).  To make the related cursor movement a little faster, we use the
1673  * save/restore cursor capabilities if the terminal has them.
1674  */
1675 NCURSES_EXPORT(int)
1676 _nc_scrolln(int n, int top, int bot, int maxy)
1677 /* scroll region from top to bot by n lines */
1678 {
1679     NCURSES_CH_T blank = ClrBlank(stdscr);
1680     int i;
1681     bool cursor_saved = FALSE;
1682     int res;
1683
1684     TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy));
1685
1686 #if USE_XMC_SUPPORT
1687     /*
1688      * If we scroll, we might remove a cookie.
1689      */
1690     if (magic_cookie_glitch > 0) {
1691         return (ERR);
1692     }
1693 #endif
1694
1695     if (n > 0) {                /* scroll up (forward) */
1696         /*
1697          * Explicitly clear if stuff pushed off top of region might
1698          * be saved by the terminal.
1699          */
1700         res = scroll_csr_forward(n, top, bot, 0, maxy, blank);
1701
1702         if (res == ERR && change_scroll_region) {
1703             if ((((n == 1 && scroll_forward) || parm_index)
1704                  && (SP->_cursrow == bot || SP->_cursrow == bot - 1))
1705                 && save_cursor && restore_cursor) {
1706                 cursor_saved = TRUE;
1707                 TPUTS_TRACE("save_cursor");
1708                 putp(save_cursor);
1709             }
1710             TPUTS_TRACE("change_scroll_region");
1711             putp(tparm(change_scroll_region, top, bot));
1712             if (cursor_saved) {
1713                 TPUTS_TRACE("restore_cursor");
1714                 putp(restore_cursor);
1715             } else {
1716                 SP->_cursrow = SP->_curscol = -1;
1717             }
1718
1719             res = scroll_csr_forward(n, top, bot, top, bot, blank);
1720
1721             TPUTS_TRACE("change_scroll_region");
1722             putp(tparm(change_scroll_region, 0, maxy));
1723             SP->_cursrow = SP->_curscol = -1;
1724         }
1725
1726         if (res == ERR && _nc_idlok)
1727             res = scroll_idl(n, top, bot - n + 1, blank);
1728
1729         /*
1730          * Clear the newly shifted-in text.
1731          */
1732         if (res != ERR
1733             && (non_dest_scroll_region || (memory_below && bot == maxy))) {
1734             NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
1735             if (bot == maxy && clr_eos) {
1736                 GoTo(bot - n + 1, 0);
1737                 ClrToEOS(blank2);
1738             } else {
1739                 for (i = 0; i < n; i++) {
1740                     GoTo(bot - i, 0);
1741                     ClrToEOL(blank2, FALSE);
1742                 }
1743             }
1744         }
1745
1746     } else {                    /* (n < 0) - scroll down (backward) */
1747         res = scroll_csr_backward(-n, top, bot, 0, maxy, blank);
1748
1749         if (res == ERR && change_scroll_region) {
1750             if (top != 0 && (SP->_cursrow == top || SP->_cursrow == top - 1)
1751                 && save_cursor && restore_cursor) {
1752                 cursor_saved = TRUE;
1753                 TPUTS_TRACE("save_cursor");
1754                 putp(save_cursor);
1755             }
1756             TPUTS_TRACE("change_scroll_region");
1757             putp(tparm(change_scroll_region, top, bot));
1758             if (cursor_saved) {
1759                 TPUTS_TRACE("restore_cursor");
1760                 putp(restore_cursor);
1761             } else {
1762                 SP->_cursrow = SP->_curscol = -1;
1763             }
1764
1765             res = scroll_csr_backward(-n, top, bot, top, bot, blank);
1766
1767             TPUTS_TRACE("change_scroll_region");
1768             putp(tparm(change_scroll_region, 0, maxy));
1769             SP->_cursrow = SP->_curscol = -1;
1770         }
1771
1772         if (res == ERR && _nc_idlok)
1773             res = scroll_idl(-n, bot + n + 1, top, blank);
1774
1775         /*
1776          * Clear the newly shifted-in text.
1777          */
1778         if (res != ERR
1779             && (non_dest_scroll_region || (memory_above && top == 0))) {
1780             NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
1781             for (i = 0; i < -n; i++) {
1782                 GoTo(i + top, 0);
1783                 ClrToEOL(blank2, FALSE);
1784             }
1785         }
1786     }
1787
1788     if (res == ERR)
1789         return (ERR);
1790
1791     _nc_scroll_window(curscr, n, top, bot, blank);
1792
1793     /* shift hash values too - they can be reused */
1794     _nc_scroll_oldhash(n, top, bot);
1795
1796     return (OK);
1797 }
1798
1799 NCURSES_EXPORT(void)
1800 _nc_screen_resume(void)
1801 {
1802     /* make sure terminal is in a sane known state */
1803     SP->_current_attr = A_NORMAL;
1804     newscr->_clear = TRUE;
1805
1806     /* reset color pairs and definitions */
1807     if (SP->_coloron || SP->_color_defs)
1808         _nc_reset_colors();
1809
1810     /* restore user-defined colors, if any */
1811     if (SP->_color_defs < 0) {
1812         int n;
1813         SP->_color_defs = -(SP->_color_defs);
1814         for (n = 0; n < SP->_color_defs; ++n) {
1815             if (SP->_color_table[n].init) {
1816                 init_color(n,
1817                            SP->_color_table[n].r,
1818                            SP->_color_table[n].g,
1819                            SP->_color_table[n].b);
1820             }
1821         }
1822     }
1823
1824     if (exit_attribute_mode)
1825         putp(exit_attribute_mode);
1826     else {
1827         /* turn off attributes */
1828         if (exit_alt_charset_mode)
1829             putp(exit_alt_charset_mode);
1830         if (exit_standout_mode)
1831             putp(exit_standout_mode);
1832         if (exit_underline_mode)
1833             putp(exit_underline_mode);
1834     }
1835     if (exit_insert_mode)
1836         putp(exit_insert_mode);
1837     if (enter_am_mode && exit_am_mode)
1838         putp(auto_right_margin ? enter_am_mode : exit_am_mode);
1839 }
1840
1841 NCURSES_EXPORT(void)
1842 _nc_screen_init(void)
1843 {
1844     _nc_screen_resume();
1845 }
1846
1847 /* wrap up screen handling */
1848 NCURSES_EXPORT(void)
1849 _nc_screen_wrap(void)
1850 {
1851     UpdateAttrs(A_NORMAL);
1852 #if NCURSES_EXT_FUNCS
1853     if (SP->_coloron
1854         && !SP->_default_color) {
1855         NCURSES_CH_T blank = NewChar(BLANK_TEXT);
1856         SP->_default_color = TRUE;
1857         _nc_do_color(-1, 0, FALSE, _nc_outch);
1858         SP->_default_color = FALSE;
1859
1860         mvcur(SP->_cursrow, SP->_curscol, screen_lines - 1, 0);
1861
1862         ClrToEOL(blank, TRUE);
1863     }
1864 #endif
1865     if (SP->_color_defs) {
1866         _nc_reset_colors();
1867     }
1868 }
1869
1870 #if USE_XMC_SUPPORT
1871 NCURSES_EXPORT(void)
1872 _nc_do_xmc_glitch(attr_t previous)
1873 {
1874     attr_t chg = XMC_CHANGES(previous ^ SP->_current_attr);
1875
1876     while (chg != 0) {
1877         if (chg & 1) {
1878             SP->_curscol += magic_cookie_glitch;
1879             if (SP->_curscol >= SP->_columns)
1880                 wrap_cursor();
1881             TR(TRACE_UPDATE, ("bumped to %d,%d after cookie", SP->_cursrow, SP->_curscol));
1882         }
1883         chg >>= 1;
1884     }
1885 }
1886 #endif /* USE_XMC_SUPPORT */