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