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