xref: /original-bsd/lib/libcurses/refresh.c (revision 4a884f8b)
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.36 (Berkeley) 03/02/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 short 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 	wx = *win->lines[wy]->firstchp - win->ch_off;
185 	if (wx >= win->maxx)
186 		return (OK);
187 	else if (wx < 0)
188 		wx = 0;
189 	lch = *win->lines[wy]->lastchp - win->ch_off;
190 	if (lch < 0)
191 		return (OK);
192 	else if (lch >= win->maxx)
193 		lch = win->maxx - 1;
194 	y = wy + win->begy;
195 
196 	if (curwin)
197 		csp = &blank;
198 	else
199 		csp = &curscr->lines[wy + win->begy]->line[wx + win->begx];
200 
201 	nsp = &win->lines[wy]->line[wx];
202 	force = win->lines[wy]->flags & __FORCEPAINT;
203 	win->lines[wy]->flags &= ~__FORCEPAINT;
204 	if (CE && !curwin) {
205 		for (cp = &win->lines[wy]->line[win->maxx - 1];
206 		     cp->ch == ' ' && cp->attr == 0; cp--)
207 			if (cp <= win->lines[wy]->line)
208 				break;
209 		nlsp = cp - win->lines[wy]->line;
210 	}
211 	if (!curwin)
212 		ce = CE;
213 	else
214 		ce = NULL;
215 
216 	if (force) {
217 		if (CM)
218 			tputs(tgoto(CM, lx, ly), 0, __cputchar);
219 		else {
220 			tputs(HO, 0, __cputchar);
221 			mvcur(0, 0, ly, lx);
222 		}
223 	}
224 	while (wx <= lch) {
225 		if (!force && memcmp(nsp, csp, sizeof(__LDATA)) == 0) {
226 			if (wx <= lch) {
227 				while (memcmp(nsp, csp, sizeof(__LDATA)) == 0 &&
228 			            wx <= lch) {
229 					    nsp++;
230 					    if (!curwin)
231 						    csp++;
232 					    ++wx;
233 				    }
234 				continue;
235 			}
236 			break;
237 		}
238 		domvcur(ly, lx, y, wx + win->begx);
239 
240 #ifdef DEBUG
241 		__TRACE("makech: 1: wx = %d, ly= %d, lx = %d, newy = %d, newx = %d, force =%d\n",
242 		    wx, ly, lx, y, wx + win->begx, force);
243 #endif
244 		ly = y;
245 		lx = wx + win->begx;
246 		while ((force || memcmp(nsp, csp, sizeof(__LDATA)) != 0)
247 		    && wx <= lch) {
248 
249 			/* Enter/exit standout mode as appropriate. */
250 			if (SO && (nsp->attr & __STANDOUT) !=
251 			    (curscr->flags & __WSTANDOUT)) {
252 				if (nsp->attr & __STANDOUT) {
253 					tputs(SO, 0, __cputchar);
254 					curscr->flags |= __WSTANDOUT;
255 				} else {
256 					tputs(SE, 0, __cputchar);
257 					curscr->flags &= ~__WSTANDOUT;
258 				}
259 			}
260 
261 			wx++;
262 			if (wx >= win->maxx && wy == win->maxy - 1 && !curwin)
263 				if (win->flags & __SCROLLOK) {
264 					if (curscr->flags & __WSTANDOUT
265 					    && win->flags & __ENDLINE)
266 						if (!MS) {
267 							tputs(SE, 0,
268 							    __cputchar);
269 							curscr->flags &=
270 							    ~__WSTANDOUT;
271 						}
272 					if (!curwin) {
273 						csp->attr = nsp->attr;
274 						putchar(csp->ch = nsp->ch);
275 					} else
276 						putchar(nsp->ch);
277 
278 					if (wx + win->begx < curscr->maxx) {
279 						domvcur(ly, wx + win->begx,
280 						    win->begy + win->maxy - 1,
281 						    win->begx + win->maxx - 1);
282 					}
283 					ly = win->begy + win->maxy - 1;
284 					lx = win->begx + win->maxx - 1;
285 					return (OK);
286 				} else
287 					if (win->flags & __SCROLLWIN) {
288 						lx = --wx;
289 						return (ERR);
290 					}
291 			if (!curwin) {
292 				csp->attr = nsp->attr;
293 				putchar(csp->ch = nsp->ch);
294 				csp++;
295 		       	} else
296 				putchar(nsp->ch);
297 
298 #ifdef DEBUG
299 			__TRACE("makech: putchar(%c)\n", nsp->ch & 0177);
300 #endif
301 			if (UC && (nsp->attr & __STANDOUT)) {
302 				putchar('\b');
303 				tputs(UC, 0, __cputchar);
304 			}
305 			nsp++;
306 		}
307 #ifdef DEBUG
308 		__TRACE("makech: 2: wx = %d, lx = %d\n", wx, lx);
309 #endif
310 		if (lx == wx + win->begx)	/* If no change. */
311 			break;
312 		lx = wx + win->begx;
313 		if (lx >= COLS && AM) {
314 			if (wy != LINES)
315 				win->lines[wy]->flags |= __ISPASTEOL;
316 			lx = COLS - 1;
317 		} else if (wx >= win->maxx) {
318 			if (wy != win->maxy)
319 				win->lines[wy]->flags |= __ISPASTEOL;
320 			domvcur(ly, lx, ly, win->maxx + win->begx - 1);
321 			lx = win->maxx + win->begx - 1;
322 		}
323 
324 #ifdef DEBUG
325 		__TRACE("makech: 3: wx = %d, lx = %d\n", wx, lx);
326 #endif
327 	}
328 	return (OK);
329 }
330 
331 /*
332  * domvcur --
333  *	Do a mvcur, leaving standout mode if necessary.
334  */
335 static void
336 domvcur(oy, ox, ny, nx)
337 	int oy, ox, ny, nx;
338 {
339 	if (curscr->flags & __WSTANDOUT && !MS) {
340 		tputs(SE, 0, __cputchar);
341 		curscr->flags &= ~__WSTANDOUT;
342 	}
343 
344 	mvcur(oy, ox, ny, nx);
345 }
346 
347 /*
348  * Quickch() attempts to detect a pattern in the change of the window
349  * in order to optimize the change, e.g., scroll n lines as opposed to
350  * repainting the screen line by line.
351  */
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 	/*
392 	 * Search for the largest block of text not changed.
393 	 * Invariants of the loop:
394 	 * - Startw is the index of the beginning of the examined block in win.
395          * - Starts is the index of the beginning of the examined block in
396 	 *    curscr.
397 	 * - Curs is the index of one past the end of the exmined block in win.
398 	 * - Curw is the index of one past the end of the exmined block in
399 	 *   curscr.
400 	 * - bsize is the current size of the examined block.
401          */
402 	for (bsize = bot - top; bsize >= THRESH; bsize--) {
403 		for (startw = top; startw <= bot - bsize; startw++)
404 			for (starts = top; starts <= bot - bsize;
405 			     starts++) {
406 				for (curw = startw, curs = starts;
407 				     curs < starts + bsize; curw++, curs++)
408 					if (win->lines[curw]->flags &
409 					    __FORCEPAINT ||
410 					    (win->lines[curw]->hash !=
411 					    curscr->lines[curs]->hash ||
412 				            memcmp(win->lines[curw]->line,
413 					    curscr->lines[curs]->line,
414 					    win->maxx * __LDATASIZE) != 0))
415 						break;
416 				if (curs == starts + bsize)
417 					goto done;
418 			}
419 	}
420  done:
421 	/* Did not find anything */
422 	if (bsize < THRESH)
423 		return;
424 
425 #ifdef DEBUG
426 	__TRACE("quickch:bsize=%d,starts=%d,startw=%d,curw=%d,curs=%d,top=%d,bot=%d\n",
427 		bsize, starts, startw, curw, curs, top, bot);
428 #endif
429 
430 	/*
431 	 * Make sure that there is no overlap between the bottom and top
432 	 * regions and the middle scrolled block.
433 	 */
434 	if (bot < curs)
435 		bot = curs - 1;
436 	if (top > starts)
437 		top = starts;
438 
439 	n = startw - starts;
440 
441 #ifdef DEBUG
442 		__TRACE("#####################################\n");
443 		for (i = 0; i < curscr->maxy; i++) {
444 			__TRACE("C: %d:", i);
445 			__TRACE(" 0x%x \n", curscr->lines[i]->hash);
446 			for (j = 0; j < curscr->maxx; j++)
447 				__TRACE("%c",
448 			           curscr->lines[i]->line[j].ch);
449 			__TRACE("\n");
450 			for (j = 0; j < curscr->maxx; j++)
451 				__TRACE("%x",
452 			           curscr->lines[i]->line[j].attr);
453 			__TRACE("\n");
454 			__TRACE("W: %d:", i);
455 			__TRACE(" 0x%x \n", win->lines[i]->hash);
456 			__TRACE(" 0x%x ", win->lines[i]->flags);
457 			for (j = 0; j < win->maxx; j++)
458 				__TRACE("%c",
459 			           win->lines[i]->line[j].ch);
460 			__TRACE("\n");
461 			for (j = 0; j < win->maxx; j++)
462 				__TRACE("%x",
463 			           win->lines[i]->line[j].attr);
464 			__TRACE("\n");
465 		}
466 #endif
467 	if (n != 0)
468 		scrolln(win, starts, startw, curs, bot, top);
469 
470 	/* So we don't have to call __hash() each time */
471 	for (i = 0; i < win->maxx; i++) {
472 		buf[i].ch = ' ';
473 		buf[i].attr = 0;
474 	}
475 	blank_hash = __hash((char *) buf, win->maxx * __LDATASIZE);
476 
477 	/*
478 	 * Perform the rotation to maintain the consistency of curscr.
479 	 * This is hairy since we are doing an *in place* rotation.
480 	 * Invariants of the loop:
481 	 * - I is the index of the current line.
482 	 * - Target is the index of the target of line i.
483 	 * - Tmp1 points to current line (i).
484 	 * - Tmp2 and points to target line (target);
485 	 * - Cur_period is the index of the end of the current period.
486 	 *   (see below).
487 	 *
488 	 * There are 2 major issues here that make this rotation non-trivial:
489 	 * 1.  Scrolling in a scrolling region bounded by the top
490 	 *     and bottom regions determined (whose size is sc_region).
491 	 * 2.  As a result of the use of the mod function, there may be a
492 	 *     period introduced, i.e., 2 maps to 4, 4 to 6, n-2 to 0, and
493 	 *     0 to 2, which then causes all odd lines not to be rotated.
494 	 *     To remedy this, an index of the end ( = beginning) of the
495 	 *     current 'period' is kept, cur_period, and when it is reached,
496 	 *     the next period is started from cur_period + 1 which is
497 	 *     guaranteed not to have been reached since that would mean that
498 	 *     all records would have been reached. (think about it...).
499 	 *
500 	 * Lines in the rotation can have 3 attributes which are marked on the
501 	 * line so that curscr is consistent with the visual screen.
502 	 * 1.  Not dirty -- lines inside the scrolled block, top region or
503 	 *                  bottom region.
504 	 * 2.  Blank lines -- lines in the differential of the scrolling
505 	 *		      region adjacent to top and bot regions
506 	 *                    depending on scrolling direction.
507 	 * 3.  Dirty line -- all other lines are marked dirty.
508 	 */
509 	sc_region = bot - top + 1;
510 	i = top;
511 	tmp1 = curscr->lines[top];
512 	cur_period = top;
513 	for (j = top; j <= bot; j++) {
514 		target = (i - top + n + sc_region) % sc_region + top;
515 		tmp2 = curscr->lines[target];
516 		curscr->lines[target] = tmp1;
517 		/* Mark block as clean and blank out scrolled lines. */
518 		clp = curscr->lines[target];
519 #ifdef DEBUG
520 		__TRACE("quickch: n=%d startw=%d curw=%d i = %d target=%d ",
521 			n, startw, curw, i, target);
522 #endif
523 		if ((target >= startw && target < curw) || target < top
524 		    || target > bot) {
525 #ifdef DEBUG
526 			__TRACE("-- notdirty");
527 #endif
528 			win->lines[target]->flags &= ~__ISDIRTY;
529 		} else if ((n > 0 && target >= top && target < top + n) ||
530 		           (n < 0 && target <= bot && target > bot + n)) {
531 			if (clp->hash != blank_hash ||  memcmp(clp->line,
532 			    buf, win->maxx * __LDATASIZE) !=0) {
533 				(void)memcpy(clp->line,  buf,
534 				    win->maxx * __LDATASIZE);
535 #ifdef DEBUG
536 				__TRACE("-- blanked out: dirty");
537 #endif
538 				clp->hash = blank_hash;
539 				__touchline(win, target, 0, win->maxx - 1, 0);
540 			} else {
541 				__touchline(win, target, 0, win->maxx - 1, 0);
542 #ifdef DEBUG
543 				__TRACE(" -- blank line already: dirty");
544 #endif
545 			}
546 		} else {
547 #ifdef DEBUG
548 			__TRACE(" -- dirty");
549 #endif
550 			__touchline(win, target, 0, win->maxx - 1, 0);
551 		}
552 #ifdef DEBUG
553 		__TRACE("\n");
554 #endif
555 		if (target == cur_period) {
556 			i = target + 1;
557 			tmp1 = curscr->lines[i];
558 			cur_period = i;
559 		} else {
560 			tmp1 = tmp2;
561 			i = target;
562 		}
563 	}
564 #ifdef DEBUG
565 		__TRACE("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
566 		for (i = 0; i < curscr->maxy; i++) {
567 			__TRACE("C: %d:", i);
568 			for (j = 0; j < curscr->maxx; j++)
569 				__TRACE("%c",
570 			           curscr->lines[i]->line[j].ch);
571 			__TRACE("\n");
572 			__TRACE("W: %d:", i);
573 			for (j = 0; j < win->maxx; j++)
574 				__TRACE("%c",
575 			           win->lines[i]->line[j].ch);
576 			__TRACE("\n");
577 		}
578 #endif
579 }
580 
581 /*
582  * Scrolln performs the scroll by n lines, where n is starts - startw.
583  */
584 static void
585 scrolln(win, starts, startw, curs, bot, top)
586 	WINDOW *win;
587 	int starts, startw, curs, bot, top;
588 {
589 	int i, oy, ox, n;
590 
591 	oy = curscr->cury;
592 	ox = curscr->curx;
593 	n = starts - startw;
594 
595 	if (n > 0) {
596 		mvcur(oy, ox, top, 0);
597 		/* Scroll up the block */
598 		if (DL)
599 			tputs(__tscroll(DL, n), 0, __cputchar);
600 		else
601 			for(i = 0; i < n; i++)
602 				tputs(dl, 0, __cputchar);
603 
604 		/*
605 		 * Push down the bottom region.
606 		 */
607 		mvcur(top, 0, bot - n + 1, 0);
608 		if (AL)
609 			tputs(__tscroll(AL, n), 0, __cputchar);
610 		else
611 			for(i = 0; i < n; i++)
612 				tputs(al, 0, __cputchar);
613 		mvcur(bot - n + 1, 0, oy, ox);
614 	} else {
615 		/* Preserve the bottom lines */
616 		mvcur(oy, ox, bot + n + 1, 0);	/* n < 0 */
617 		if (DL)
618 			tputs(__tscroll(DL, -n), 0, __cputchar);
619 		else
620 		       	for(i = n; i < 0; i++)
621 				tputs(dl, 0, __cputchar);
622 		mvcur(bot + n + 1, 0, top, 0);
623 
624 		/* Scroll the block down */
625 		if (AL)
626 			tputs(__tscroll(AL, -n), 0, __cputchar);
627 		else
628 			for(i = n; i < 0; i++)
629 				tputs(al, 0, __cputchar);
630 		mvcur(top, 0, oy, ox);
631 	}
632 }
633 
634 
635