xref: /original-bsd/lib/libcurses/refresh.c (revision f919448c)
1 /*
2  * Copyright (c) 1981 Regents of the University of California.
3  * All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char sccsid[] = "@(#)refresh.c	5.38 (Berkeley) 04/27/93";
10 #endif /* not lint */
11 
12 #include <curses.h>
13 #include <string.h>
14 
15 static int curwin;
16 static short ly, lx;
17 
18 WINDOW *_win;
19 
20 static void	domvcur __P((int, int, int, int));
21 static int	makech __P((WINDOW *, int));
22 static void	quickch __P((WINDOW *));
23 static void	scrolln __P((WINDOW *, int, int, int, int, int));
24 
25 /*
26  * wrefresh --
27  *	Make the current screen look like "win" over the area coverd by
28  *	win.
29  */
30 int
31 wrefresh(win)
32 	register WINDOW *win;
33 {
34 	register __LINE *wlp;
35 	register int retval;
36 	register short wy;
37 	int dnum;
38 
39 	/* Initialize loop parameters. */
40 	ly = curscr->cury;
41 	lx = curscr->curx;
42 	wy = 0;
43 	_win = win;
44 	curwin = (win == curscr);
45 
46 	if (!curwin)
47 		for (wy = 0; wy < win->maxy; wy++) {
48 			wlp = win->lines[wy];
49 			if (wlp->flags & __ISDIRTY)
50 				wlp->hash =
51 				   __hash((char *) wlp->line, win->maxx * __LDATASIZE);
52 		}
53 
54 	if (win->flags & __CLEAROK || curscr->flags & __CLEAROK || curwin) {
55 		if ((win->flags & __FULLWIN) || curscr->flags & __CLEAROK) {
56 			tputs(CL, 0, __cputchar);
57 			ly = 0;
58 			lx = 0;
59 			if (!curwin) {
60 				curscr->flags &= ~__CLEAROK;
61 				curscr->cury = 0;
62 				curscr->curx = 0;
63 				werase(curscr);
64 			}
65 			__touchwin(win);
66 		}
67 		win->flags &= ~__CLEAROK;
68 	}
69 	if (!CA) {
70 		if (win->curx != 0)
71 			putchar('\n');
72 		if (!curwin)
73 			werase(curscr);
74 	}
75 #ifdef DEBUG
76 	__TRACE("wrefresh: (%0.2o): curwin = %d\n", win, curwin);
77 	__TRACE("wrefresh: \tfirstch\tlastch\n");
78 #endif
79 
80 #ifndef NOQCH
81 	if ((win->flags & __FULLWIN) && !curwin) {
82 		/*
83 		 * Invoke quickch() only if more than a quarter of the lines
84 		 * in the window are dirty.
85 		 */
86 		for (wy = 0, dnum = 0; wy < win->maxy; wy++)
87 			if (win->lines[wy]->flags & (__ISDIRTY | __FORCEPAINT))
88 				dnum++;
89 		if (!__noqch && dnum > (int) win->maxy / 4)
90 			quickch(win);
91 	}
92 #endif
93 	for (wy = 0; wy < win->maxy; wy++) {
94 #ifdef DEBUG
95 		__TRACE("%d\t%d\t%d\n",
96 		    wy, *win->lines[wy]->firstchp, *win->lines[wy]->lastchp);
97 #endif
98 		if (!curwin)
99 			curscr->lines[wy]->hash = win->lines[wy]->hash;
100 		if (win->lines[wy]->flags & (__ISDIRTY | __FORCEPAINT)) {
101 			if (makech(win, wy) == ERR)
102 				return (ERR);
103 			else {
104 				if (*win->lines[wy]->firstchp >= win->ch_off)
105 					*win->lines[wy]->firstchp = win->maxx +
106 					    win->ch_off;
107 				if (*win->lines[wy]->lastchp < win->maxx +
108 				    win->ch_off)
109 					*win->lines[wy]->lastchp = win->ch_off;
110 				if (*win->lines[wy]->lastchp <
111 				    *win->lines[wy]->firstchp) {
112 #ifdef DEBUG
113 					__TRACE("wrefresh: line %d notdirty \n", wy);
114 #endif
115 					win->lines[wy]->flags &= ~__ISDIRTY;
116 				}
117 			}
118 
119 		} else
120 			win->lines[wy]->flags &= ~__ISPASTEOL;
121 #ifdef DEBUG
122 		__TRACE("\t%d\t%d\n", *win->lines[wy]->firstchp,
123 			*win->lines[wy]->lastchp);
124 #endif
125 	}
126 
127 #ifdef DEBUG
128 	__TRACE("refresh: ly=%d, lx=%d\n", ly, lx);
129 #endif
130 
131 	if (win == curscr)
132 		domvcur(ly, lx, win->cury, win->curx);
133 	else {
134 		if (win->flags & __LEAVEOK) {
135 			curscr->cury = ly;
136 			curscr->curx = lx;
137 			ly -= win->begy;
138 			lx -= win->begx;
139 			if (ly >= 0 && ly < win->maxy && lx >= 0 &&
140 			    lx < win->maxx) {
141 				win->cury = ly;
142 				win->curx = lx;
143 			} else
144 				win->cury = win->curx = 0;
145 		} else {
146 			domvcur(ly, lx, win->cury + win->begy,
147 			    win->curx + win->begx);
148 			curscr->cury = win->cury + win->begy;
149 			curscr->curx = win->curx + win->begx;
150 		}
151 	}
152 	retval = OK;
153 
154 	_win = NULL;
155 	(void)fflush(stdout);
156 	return (retval);
157 }
158 
159 /*
160  * makech --
161  *	Make a change on the screen.
162  */
163 static int
164 makech(win, wy)
165 	register WINDOW *win;
166 	int wy;
167 {
168 	register int nlsp;		/* Last space in lines. */
169 	register int wx, lch, y;
170 	register __LDATA *nsp, *csp, *cp;
171 	u_int force;
172 	char *ce;
173 	__LDATA blank = {' ', 0};
174 
175 	/* Is the cursor still on the end of the last line? */
176 	if (wy > 0 && win->lines[wy - 1]->flags & __ISPASTEOL) {
177 		win->lines[wy - 1]->flags &= ~__ISPASTEOL;
178 		domvcur(ly, lx, ly + 1, 0);
179 		ly++;
180 		lx = 0;
181 	}
182 	if (!(win->lines[wy]->flags & __ISDIRTY))
183 		return (OK);
184 
185 	wx = *win->lines[wy]->firstchp - win->ch_off;
186 	if (wx < 0)
187 		wx = 0;
188 	else if (wx >= win->maxx)
189 		return (OK);
190 	lch = *win->lines[wy]->lastchp - win->ch_off;
191 	if (lch < 0)
192 		return (OK);
193 	else if (lch >= (int) win->maxx)
194 		lch = win->maxx - 1;
195 	y = wy + win->begy;
196 
197 	if (curwin)
198 		csp = &blank;
199 	else
200 		csp = &curscr->lines[wy + win->begy]->line[wx + win->begx];
201 
202 	nsp = &win->lines[wy]->line[wx];
203 	force = win->lines[wy]->flags & __FORCEPAINT;
204 	win->lines[wy]->flags &= ~__FORCEPAINT;
205 	if (CE && !curwin) {
206 		for (cp = &win->lines[wy]->line[win->maxx - 1];
207 		     cp->ch == ' ' && cp->attr == 0; cp--)
208 			if (cp <= win->lines[wy]->line)
209 				break;
210 		nlsp = cp - win->lines[wy]->line;
211 	}
212 	if (!curwin)
213 		ce = CE;
214 	else
215 		ce = NULL;
216 
217 	if (force) {
218 		if (CM)
219 			tputs(tgoto(CM, lx, ly), 0, __cputchar);
220 		else {
221 			tputs(HO, 0, __cputchar);
222 			mvcur(0, 0, ly, lx);
223 		}
224 	}
225 	while (wx <= lch) {
226 		if (!force && memcmp(nsp, csp, sizeof(__LDATA)) == 0) {
227 			if (wx <= lch) {
228 				while (memcmp(nsp, csp, sizeof(__LDATA)) == 0 &&
229 			            wx <= lch) {
230 					    nsp++;
231 					    if (!curwin)
232 						    csp++;
233 					    ++wx;
234 				    }
235 				continue;
236 			}
237 			break;
238 		}
239 		domvcur(ly, lx, y, wx + win->begx);
240 
241 #ifdef DEBUG
242 		__TRACE("makech: 1: wx = %d, ly= %d, lx = %d, newy = %d, newx = %d, force =%d\n",
243 		    wx, ly, lx, y, wx + win->begx, force);
244 #endif
245 		ly = y;
246 		lx = wx + win->begx;
247 		while ((force || memcmp(nsp, csp, sizeof(__LDATA)) != 0)
248 		    && wx <= lch) {
249 
250 			/* Enter/exit standout mode as appropriate. */
251 			if (SO && (nsp->attr & __STANDOUT) !=
252 			    (curscr->flags & __WSTANDOUT)) {
253 				if (nsp->attr & __STANDOUT) {
254 					tputs(SO, 0, __cputchar);
255 					curscr->flags |= __WSTANDOUT;
256 				} else {
257 					tputs(SE, 0, __cputchar);
258 					curscr->flags &= ~__WSTANDOUT;
259 				}
260 			}
261 
262 			wx++;
263 			if (wx >= win->maxx && wy == win->maxy - 1 && !curwin)
264 				if (win->flags & __SCROLLOK) {
265 					if (curscr->flags & __WSTANDOUT
266 					    && win->flags & __ENDLINE)
267 						if (!MS) {
268 							tputs(SE, 0,
269 							    __cputchar);
270 							curscr->flags &=
271 							    ~__WSTANDOUT;
272 						}
273 					if (!curwin) {
274 						csp->attr = nsp->attr;
275 						putchar(csp->ch = nsp->ch);
276 					} else
277 						putchar(nsp->ch);
278 
279 					if (wx + win->begx < curscr->maxx) {
280 						domvcur(ly, wx + win->begx,
281 						    win->begy + win->maxy - 1,
282 						    win->begx + win->maxx - 1);
283 					}
284 					ly = win->begy + win->maxy - 1;
285 					lx = win->begx + win->maxx - 1;
286 					return (OK);
287 				} else
288 					if (win->flags & __SCROLLWIN) {
289 						lx = --wx;
290 						return (ERR);
291 					}
292 			if (!curwin) {
293 				csp->attr = nsp->attr;
294 				putchar(csp->ch = nsp->ch);
295 				csp++;
296 		       	} else
297 				putchar(nsp->ch);
298 
299 #ifdef DEBUG
300 			__TRACE("makech: putchar(%c)\n", nsp->ch & 0177);
301 #endif
302 			if (UC && (nsp->attr & __STANDOUT)) {
303 				putchar('\b');
304 				tputs(UC, 0, __cputchar);
305 			}
306 			nsp++;
307 		}
308 #ifdef DEBUG
309 		__TRACE("makech: 2: wx = %d, lx = %d\n", wx, lx);
310 #endif
311 		if (lx == wx + win->begx)	/* If no change. */
312 			break;
313 		lx = wx + win->begx;
314 		if (lx >= COLS && AM) {
315 			if (wy != LINES)
316 				win->lines[wy]->flags |= __ISPASTEOL;
317 			lx = COLS - 1;
318 		} else if (wx >= win->maxx) {
319 			if (wy != win->maxy)
320 				win->lines[wy]->flags |= __ISPASTEOL;
321 			domvcur(ly, lx, ly, win->maxx + win->begx - 1);
322 			lx = win->maxx + win->begx - 1;
323 		}
324 
325 #ifdef DEBUG
326 		__TRACE("makech: 3: wx = %d, lx = %d\n", wx, lx);
327 #endif
328 	}
329 	return (OK);
330 }
331 
332 /*
333  * domvcur --
334  *	Do a mvcur, leaving standout mode if necessary.
335  */
336 static void
337 domvcur(oy, ox, ny, nx)
338 	int oy, ox, ny, nx;
339 {
340 	if (curscr->flags & __WSTANDOUT && !MS) {
341 		tputs(SE, 0, __cputchar);
342 		curscr->flags &= ~__WSTANDOUT;
343 	}
344 
345 	mvcur(oy, ox, ny, nx);
346 }
347 
348 /*
349  * Quickch() attempts to detect a pattern in the change of the window
350  * in order to optimize the change, e.g., scroll n lines as opposed to
351  * repainting the screen line by line.
352  */
353 
354 static void
355 quickch(win)
356 	WINDOW *win;
357 {
358 #define THRESH		(int) win->maxy / 4
359 
360 	register __LINE *clp, *tmp1, *tmp2;
361 	register int bsize, curs, curw, starts, startw, i, j;
362 	int n, target, cur_period, bot, top, sc_region;
363 	__LDATA buf[1024];
364 	u_int blank_hash;
365 
366 	/*
367 	 * Find how many lines from the top of the screen are unchanged.
368 	 */
369 	for (top = 0; top < win->maxy; top++)
370 		if (win->lines[top]->flags & __FORCEPAINT ||
371 		    win->lines[top]->hash != curscr->lines[top]->hash
372 		    || memcmp(win->lines[top]->line,
373 		    curscr->lines[top]->line,
374 		    win->maxx * __LDATASIZE) != 0)
375 			break;
376 		else
377 			win->lines[top]->flags &= ~__ISDIRTY;
378        /*
379 	* Find how many lines from bottom of screen are unchanged.
380 	*/
381 	for (bot = win->maxy - 1; bot >= 0; bot--)
382 		if (win->lines[bot]->flags & __FORCEPAINT ||
383 		    win->lines[bot]->hash != curscr->lines[bot]->hash
384 		    || memcmp(win->lines[bot]->line,
385 		    curscr->lines[bot]->line,
386 		    win->maxx * __LDATASIZE) != 0)
387 			break;
388 		else
389 			win->lines[bot]->flags &= ~__ISDIRTY;
390 
391 #ifdef NO_JERKINESS
392 	/*
393 	 * If we have a bottom unchanged region return.  Scrolling the
394 	 * bottom region up and then back down causes a screen jitter.
395 	 * This will increase the number of characters sent to the screen
396 	 * but it looks better.
397 	 */
398 	if (bot < win->maxy - 1)
399 		return;
400 #endif /* NO_JERKINESS */
401 
402 	/*
403 	 * Search for the largest block of text not changed.
404 	 * Invariants of the loop:
405 	 * - Startw is the index of the beginning of the examined block in win.
406          * - Starts is the index of the beginning of the examined block in
407 	 *    curscr.
408 	 * - Curs is the index of one past the end of the exmined block in win.
409 	 * - Curw is the index of one past the end of the exmined block in
410 	 *   curscr.
411 	 * - bsize is the current size of the examined block.
412          */
413 	for (bsize = bot - top; bsize >= THRESH; bsize--) {
414 		for (startw = top; startw <= bot - bsize; startw++)
415 			for (starts = top; starts <= bot - bsize;
416 			     starts++) {
417 				for (curw = startw, curs = starts;
418 				     curs < starts + bsize; curw++, curs++)
419 					if (win->lines[curw]->flags &
420 					    __FORCEPAINT ||
421 					    (win->lines[curw]->hash !=
422 					    curscr->lines[curs]->hash ||
423 				            memcmp(win->lines[curw]->line,
424 					    curscr->lines[curs]->line,
425 					    win->maxx * __LDATASIZE) != 0))
426 						break;
427 				if (curs == starts + bsize)
428 					goto done;
429 			}
430 	}
431  done:
432 	/* Did not find anything */
433 	if (bsize < THRESH)
434 		return;
435 
436 #ifdef DEBUG
437 	__TRACE("quickch:bsize=%d,starts=%d,startw=%d,curw=%d,curs=%d,top=%d,bot=%d\n",
438 		bsize, starts, startw, curw, curs, top, bot);
439 #endif
440 
441 	/*
442 	 * Make sure that there is no overlap between the bottom and top
443 	 * regions and the middle scrolled block.
444 	 */
445 	if (bot < curs)
446 		bot = curs - 1;
447 	if (top > starts)
448 		top = starts;
449 
450 	n = startw - starts;
451 
452 #ifdef DEBUG
453 		__TRACE("#####################################\n");
454 		for (i = 0; i < curscr->maxy; i++) {
455 			__TRACE("C: %d:", i);
456 			__TRACE(" 0x%x \n", curscr->lines[i]->hash);
457 			for (j = 0; j < curscr->maxx; j++)
458 				__TRACE("%c",
459 			           curscr->lines[i]->line[j].ch);
460 			__TRACE("\n");
461 			for (j = 0; j < curscr->maxx; j++)
462 				__TRACE("%x",
463 			           curscr->lines[i]->line[j].attr);
464 			__TRACE("\n");
465 			__TRACE("W: %d:", i);
466 			__TRACE(" 0x%x \n", win->lines[i]->hash);
467 			__TRACE(" 0x%x ", win->lines[i]->flags);
468 			for (j = 0; j < win->maxx; j++)
469 				__TRACE("%c",
470 			           win->lines[i]->line[j].ch);
471 			__TRACE("\n");
472 			for (j = 0; j < win->maxx; j++)
473 				__TRACE("%x",
474 			           win->lines[i]->line[j].attr);
475 			__TRACE("\n");
476 		}
477 #endif
478 	if (n != 0)
479 		scrolln(win, starts, startw, curs, bot, top);
480 
481 	/* So we don't have to call __hash() each time */
482 	for (i = 0; i < win->maxx; i++) {
483 		buf[i].ch = ' ';
484 		buf[i].attr = 0;
485 	}
486 	blank_hash = __hash((char *) buf, win->maxx * __LDATASIZE);
487 
488 	/*
489 	 * Perform the rotation to maintain the consistency of curscr.
490 	 * This is hairy since we are doing an *in place* rotation.
491 	 * Invariants of the loop:
492 	 * - I is the index of the current line.
493 	 * - Target is the index of the target of line i.
494 	 * - Tmp1 points to current line (i).
495 	 * - Tmp2 and points to target line (target);
496 	 * - Cur_period is the index of the end of the current period.
497 	 *   (see below).
498 	 *
499 	 * There are 2 major issues here that make this rotation non-trivial:
500 	 * 1.  Scrolling in a scrolling region bounded by the top
501 	 *     and bottom regions determined (whose size is sc_region).
502 	 * 2.  As a result of the use of the mod function, there may be a
503 	 *     period introduced, i.e., 2 maps to 4, 4 to 6, n-2 to 0, and
504 	 *     0 to 2, which then causes all odd lines not to be rotated.
505 	 *     To remedy this, an index of the end ( = beginning) of the
506 	 *     current 'period' is kept, cur_period, and when it is reached,
507 	 *     the next period is started from cur_period + 1 which is
508 	 *     guaranteed not to have been reached since that would mean that
509 	 *     all records would have been reached. (think about it...).
510 	 *
511 	 * Lines in the rotation can have 3 attributes which are marked on the
512 	 * line so that curscr is consistent with the visual screen.
513 	 * 1.  Not dirty -- lines inside the scrolled block, top region or
514 	 *                  bottom region.
515 	 * 2.  Blank lines -- lines in the differential of the scrolling
516 	 *		      region adjacent to top and bot regions
517 	 *                    depending on scrolling direction.
518 	 * 3.  Dirty line -- all other lines are marked dirty.
519 	 */
520 	sc_region = bot - top + 1;
521 	i = top;
522 	tmp1 = curscr->lines[top];
523 	cur_period = top;
524 	for (j = top; j <= bot; j++) {
525 		target = (i - top + n + sc_region) % sc_region + top;
526 		tmp2 = curscr->lines[target];
527 		curscr->lines[target] = tmp1;
528 		/* Mark block as clean and blank out scrolled lines. */
529 		clp = curscr->lines[target];
530 #ifdef DEBUG
531 		__TRACE("quickch: n=%d startw=%d curw=%d i = %d target=%d ",
532 			n, startw, curw, i, target);
533 #endif
534 		if ((target >= startw && target < curw) || target < top
535 		    || target > bot) {
536 #ifdef DEBUG
537 			__TRACE("-- notdirty");
538 #endif
539 			win->lines[target]->flags &= ~__ISDIRTY;
540 		} else if ((n > 0 && target >= top && target < top + n) ||
541 		           (n < 0 && target <= bot && target > bot + n)) {
542 			if (clp->hash != blank_hash ||  memcmp(clp->line,
543 			    buf, win->maxx * __LDATASIZE) !=0) {
544 				(void)memcpy(clp->line,  buf,
545 				    win->maxx * __LDATASIZE);
546 #ifdef DEBUG
547 				__TRACE("-- blanked out: dirty");
548 #endif
549 				clp->hash = blank_hash;
550 				__touchline(win, target, 0, win->maxx - 1, 0);
551 			} else {
552 				__touchline(win, target, 0, win->maxx - 1, 0);
553 #ifdef DEBUG
554 				__TRACE(" -- blank line already: dirty");
555 #endif
556 			}
557 		} else {
558 #ifdef DEBUG
559 			__TRACE(" -- dirty");
560 #endif
561 			__touchline(win, target, 0, win->maxx - 1, 0);
562 		}
563 #ifdef DEBUG
564 		__TRACE("\n");
565 #endif
566 		if (target == cur_period) {
567 			i = target + 1;
568 			tmp1 = curscr->lines[i];
569 			cur_period = i;
570 		} else {
571 			tmp1 = tmp2;
572 			i = target;
573 		}
574 	}
575 #ifdef DEBUG
576 		__TRACE("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
577 		for (i = 0; i < curscr->maxy; i++) {
578 			__TRACE("C: %d:", i);
579 			for (j = 0; j < curscr->maxx; j++)
580 				__TRACE("%c",
581 			           curscr->lines[i]->line[j].ch);
582 			__TRACE("\n");
583 			__TRACE("W: %d:", i);
584 			for (j = 0; j < win->maxx; j++)
585 				__TRACE("%c",
586 			           win->lines[i]->line[j].ch);
587 			__TRACE("\n");
588 		}
589 #endif
590 }
591 
592 /*
593  * Scrolln performs the scroll by n lines, where n is starts - startw.
594  */
595 static void
596 scrolln(win, starts, startw, curs, bot, top)
597 	WINDOW *win;
598 	int starts, startw, curs, bot, top;
599 {
600 	int i, oy, ox, n;
601 
602 	oy = curscr->cury;
603 	ox = curscr->curx;
604 	n = starts - startw;
605 
606 	if (n > 0) {
607 		mvcur(oy, ox, top, 0);
608 		/* Scroll up the block */
609 		if (DL)
610 			tputs(__tscroll(DL, n), 0, __cputchar);
611 		else
612 			for(i = 0; i < n; i++)
613 				tputs(dl, 0, __cputchar);
614 
615 		/*
616 		 * Push down the bottom region.
617 		 */
618 		mvcur(top, 0, bot - n + 1, 0);
619 		if (AL)
620 			tputs(__tscroll(AL, n), 0, __cputchar);
621 		else
622 			for(i = 0; i < n; i++)
623 				tputs(al, 0, __cputchar);
624 		mvcur(bot - n + 1, 0, oy, ox);
625 	} else {
626 		/* Preserve the bottom lines */
627 		mvcur(oy, ox, bot + n + 1, 0);	/* n < 0 */
628 		if (DL)
629 			tputs(__tscroll(DL, -n), 0, __cputchar);
630 		else
631 		       	for(i = n; i < 0; i++)
632 				tputs(dl, 0, __cputchar);
633 		mvcur(bot + n + 1, 0, top, 0);
634 
635 		/* Scroll the block down */
636 		if (AL)
637 			tputs(__tscroll(AL, -n), 0, __cputchar);
638 		else
639 			for(i = n; i < 0; i++)
640 				tputs(al, 0, __cputchar);
641 		mvcur(top, 0, oy, ox);
642 	}
643 }
644 
645 
646