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