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