xref: /openbsd/lib/libcurses/base/lib_addch.c (revision 73471bf0)
1 /* $OpenBSD: lib_addch.c,v 1.5 2010/01/12 23:22:05 nicm Exp $ */
2 
3 /****************************************************************************
4  * Copyright (c) 1998-2006,2008 Free Software Foundation, Inc.              *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /*
32 **	lib_addch.c
33 **
34 **	The routine waddch().
35 **
36 */
37 
38 #include <curses.priv.h>
39 #include <ctype.h>
40 
41 MODULE_ID("$Id: lib_addch.c,v 1.5 2010/01/12 23:22:05 nicm Exp $")
42 
43 static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT);
44 
45 /*
46  * Ugly microtweaking alert.  Everything from here to end of module is
47  * likely to be speed-critical -- profiling data sure says it is!
48  * Most of the important screen-painting functions are shells around
49  * waddch().  So we make every effort to reduce function-call overhead
50  * by inlining stuff, even at the cost of making wrapped copies for
51  * export.  Also we supply some internal versions that don't call the
52  * window sync hook, for use by string-put functions.
53  */
54 
55 /* Return bit mask for clearing color pair number if given ch has color */
56 #define COLOR_MASK(ch) (~(attr_t)((ch) & A_COLOR ? A_COLOR : 0))
57 
58 static NCURSES_INLINE NCURSES_CH_T
59 render_char(WINDOW *win, NCURSES_CH_T ch)
60 /* compute a rendition of the given char correct for the current context */
61 {
62     attr_t a = WINDOW_ATTRS(win);
63     int pair = GetPair(ch);
64 
65     if (ISBLANK(ch)
66 	&& AttrOf(ch) == A_NORMAL
67 	&& pair == 0) {
68 	/* color/pair in attrs has precedence over bkgrnd */
69 	ch = win->_nc_bkgd;
70 	SetAttr(ch, a | AttrOf(win->_nc_bkgd));
71 	if ((pair = GET_WINDOW_PAIR(win)) == 0)
72 	    pair = GetPair(win->_nc_bkgd);
73 	SetPair(ch, pair);
74     } else {
75 	/* color in attrs has precedence over bkgrnd */
76 	a |= AttrOf(win->_nc_bkgd) & COLOR_MASK(a);
77 	/* color in ch has precedence */
78 	if (pair == 0) {
79 	    if ((pair = GET_WINDOW_PAIR(win)) == 0)
80 		pair = GetPair(win->_nc_bkgd);
81 	}
82 #if 0
83 	if (pair > 255) {
84 	    NCURSES_CH_T fixme = ch;
85 	    SetPair(fixme, pair);
86 	}
87 #endif
88 	AddAttr(ch, (a & COLOR_MASK(AttrOf(ch))));
89 	SetPair(ch, pair);
90     }
91 
92     TR(TRACE_VIRTPUT,
93        ("render_char bkg %s (%d), attrs %s (%d) -> ch %s (%d)",
94 	_tracech_t2(1, CHREF(win->_nc_bkgd)),
95 	GetPair(win->_nc_bkgd),
96 	_traceattr(WINDOW_ATTRS(win)),
97 	GET_WINDOW_PAIR(win),
98 	_tracech_t2(3, CHREF(ch)),
99 	GetPair(ch)));
100 
101     return (ch);
102 }
103 
104 NCURSES_EXPORT(NCURSES_CH_T)
105 _nc_render(WINDOW *win, NCURSES_CH_T ch)
106 /* make render_char() visible while still allowing us to inline it below */
107 {
108     return render_char(win, ch);
109 }
110 
111 /* check if position is legal; if not, return error */
112 #ifndef NDEBUG			/* treat this like an assertion */
113 #define CHECK_POSITION(win, x, y) \
114 	if (y > win->_maxy \
115 	 || x > win->_maxx \
116 	 || y < 0 \
117 	 || x < 0) { \
118 		TR(TRACE_VIRTPUT, ("Alert! Win=%p _curx = %d, _cury = %d " \
119 				   "(_maxx = %d, _maxy = %d)", win, x, y, \
120 				   win->_maxx, win->_maxy)); \
121 		return(ERR); \
122 	}
123 #else
124 #define CHECK_POSITION(win, x, y)	/* nothing */
125 #endif
126 
127 static bool
128 newline_forces_scroll(WINDOW *win, NCURSES_SIZE_T * ypos)
129 {
130     bool result = FALSE;
131 
132     if (*ypos >= win->_regtop && *ypos == win->_regbottom) {
133 	*ypos = win->_regbottom;
134 	result = TRUE;
135     } else {
136 	*ypos += 1;
137     }
138     return result;
139 }
140 
141 /*
142  * The _WRAPPED flag is useful only for telling an application that we've just
143  * wrapped the cursor.  We don't do anything with this flag except set it when
144  * wrapping, and clear it whenever we move the cursor.  If we try to wrap at
145  * the lower-right corner of a window, we cannot move the cursor (since that
146  * wouldn't be legal).  So we return an error (which is what SVr4 does).
147  * Unlike SVr4, we can successfully add a character to the lower-right corner
148  * (Solaris 2.6 does this also, however).
149  */
150 static int
151 wrap_to_next_line(WINDOW *win)
152 {
153     win->_flags |= _WRAPPED;
154     if (newline_forces_scroll(win, &(win->_cury))) {
155 	win->_curx = win->_maxx;
156 	if (!win->_scroll)
157 	    return (ERR);
158 	scroll(win);
159     }
160     win->_curx = 0;
161     return (OK);
162 }
163 
164 #if USE_WIDEC_SUPPORT
165 static int waddch_literal(WINDOW *, NCURSES_CH_T);
166 /*
167  * Fill the given number of cells with blanks using the current background
168  * rendition.  This saves/restores the current x-position.
169  */
170 static void
171 fill_cells(WINDOW *win, int count)
172 {
173     NCURSES_CH_T blank = blankchar;
174     int save_x = win->_curx;
175     int save_y = win->_cury;
176 
177     while (count-- > 0) {
178 	if (waddch_literal(win, blank) == ERR)
179 	    break;
180     }
181     win->_curx = save_x;
182     win->_cury = save_y;
183 }
184 #endif
185 
186 /*
187  * Build up the bytes for a multibyte character, returning the length when
188  * complete (a positive number), -1 for error and -2 for incomplete.
189  */
190 #if USE_WIDEC_SUPPORT
191 NCURSES_EXPORT(int)
192 _nc_build_wch(WINDOW *win, ARG_CH_T ch)
193 {
194     char *buffer = WINDOW_EXT(win, addch_work);
195     int len;
196     int x = win->_curx;
197     int y = win->_cury;
198     mbstate_t state;
199     wchar_t result;
200 
201     if ((WINDOW_EXT(win, addch_used) != 0) &&
202 	(WINDOW_EXT(win, addch_x) != x ||
203 	 WINDOW_EXT(win, addch_y) != y)) {
204 	/* discard the incomplete multibyte character */
205 	WINDOW_EXT(win, addch_used) = 0;
206 	TR(TRACE_VIRTPUT,
207 	   ("Alert discarded multibyte on move (%d,%d) -> (%d,%d)",
208 	    WINDOW_EXT(win, addch_y), WINDOW_EXT(win, addch_x),
209 	    y, x));
210     }
211     WINDOW_EXT(win, addch_x) = x;
212     WINDOW_EXT(win, addch_y) = y;
213 
214     init_mb(state);
215     buffer[WINDOW_EXT(win, addch_used)] = (char) CharOf(CHDEREF(ch));
216     WINDOW_EXT(win, addch_used) += 1;
217     buffer[WINDOW_EXT(win, addch_used)] = '\0';
218     if ((len = mbrtowc(&result,
219 		       buffer,
220 		       WINDOW_EXT(win, addch_used), &state)) > 0) {
221 	attr_t attrs = AttrOf(CHDEREF(ch));
222 	if_EXT_COLORS(int pair = GetPair(CHDEREF(ch)));
223 	SetChar(CHDEREF(ch), result, attrs);
224 	if_EXT_COLORS(SetPair(CHDEREF(ch), pair));
225 	WINDOW_EXT(win, addch_used) = 0;
226     } else if (len == -1) {
227 	/*
228 	 * An error occurred.  We could either discard everything,
229 	 * or assume that the error was in the previous input.
230 	 * Try the latter.
231 	 */
232 	TR(TRACE_VIRTPUT, ("Alert! mbrtowc returns error"));
233 	/* handle this with unctrl() */
234 	WINDOW_EXT(win, addch_used) = 0;
235     }
236     return len;
237 }
238 #endif /* USE_WIDEC_SUPPORT */
239 
240 static
241 #if !USE_WIDEC_SUPPORT		/* cannot be inline if it is recursive */
242 NCURSES_INLINE
243 #endif
244 int
245 waddch_literal(WINDOW *win, NCURSES_CH_T ch)
246 {
247     int x;
248     int y;
249     struct ldat *line;
250 
251     x = win->_curx;
252     y = win->_cury;
253 
254     CHECK_POSITION(win, x, y);
255 
256     ch = render_char(win, ch);
257 
258     line = win->_line + y;
259 
260     CHANGED_CELL(line, x);
261 
262     /*
263      * Build up multibyte characters until we have a wide-character.
264      */
265     if_WIDEC({
266 	if (WINDOW_EXT(win, addch_used) != 0 || !Charable(ch)) {
267 	    int len = _nc_build_wch(win, CHREF(ch));
268 
269 	    if (len >= -1) {
270 		/* handle EILSEQ */
271 		if (is8bits(CharOf(ch))) {
272 		    const char *s = unctrl((chtype) CharOf(ch));
273 		    if (s[1] != 0) {
274 			return waddstr(win, s);
275 		    }
276 		}
277 		if (len == -1)
278 		    return waddch(win, ' ');
279 	    } else {
280 		return OK;
281 	    }
282 	}
283     });
284 
285     /*
286      * Non-spacing characters are added to the current cell.
287      *
288      * Spacing characters that are wider than one column require some display
289      * adjustments.
290      */
291     if_WIDEC({
292 	int len = wcwidth(CharOf(ch));
293 	int i;
294 	int j;
295 	wchar_t *chars;
296 
297 	if (len == 0) {		/* non-spacing */
298 	    if ((x > 0 && y >= 0)
299 		|| (win->_maxx >= 0 && win->_cury >= 1)) {
300 		if (x > 0 && y >= 0)
301 		    chars = (win->_line[y].text[x - 1].chars);
302 		else
303 		    chars = (win->_line[y - 1].text[win->_maxx].chars);
304 		for (i = 0; i < CCHARW_MAX; ++i) {
305 		    if (chars[i] == 0) {
306 			TR(TRACE_VIRTPUT,
307 			   ("added non-spacing %d: %x",
308 			    x, (int) CharOf(ch)));
309 			chars[i] = CharOf(ch);
310 			break;
311 		    }
312 		}
313 	    }
314 	    goto testwrapping;
315 	} else if (len > 1) {	/* multi-column characters */
316 	    /*
317 	     * Check if the character will fit on the current line.  If it does
318 	     * not fit, fill in the remainder of the line with blanks.  and
319 	     * move to the next line.
320 	     */
321 	    if (len > win->_maxx + 1) {
322 		TR(TRACE_VIRTPUT, ("character will not fit"));
323 		return ERR;
324 	    } else if (x + len > win->_maxx + 1) {
325 		int count = win->_maxx + 1 - x;
326 		TR(TRACE_VIRTPUT, ("fill %d remaining cells", count));
327 		fill_cells(win, count);
328 		if (wrap_to_next_line(win) == ERR)
329 		    return ERR;
330 		x = win->_curx;
331 		y = win->_cury;
332 	    }
333 	    /*
334 	     * Check for cells which are orphaned by adding this character, set
335 	     * those to blanks.
336 	     *
337 	     * FIXME: this actually could fill j-i cells, more complicated to
338 	     * setup though.
339 	     */
340 	    for (i = 0; i < len; ++i) {
341 		if (isWidecBase(win->_line[y].text[x + i])) {
342 		    break;
343 		} else if (isWidecExt(win->_line[y].text[x + i])) {
344 		    for (j = i; x + j <= win->_maxx; ++j) {
345 			if (!isWidecExt(win->_line[y].text[x + j])) {
346 			    TR(TRACE_VIRTPUT, ("fill %d orphan cells", j));
347 			    fill_cells(win, j);
348 			    break;
349 			}
350 		    }
351 		    break;
352 		}
353 	    }
354 	    /*
355 	     * Finally, add the cells for this character.
356 	     */
357 	    for (i = 0; i < len; ++i) {
358 		NCURSES_CH_T value = ch;
359 		SetWidecExt(value, i);
360 		TR(TRACE_VIRTPUT, ("multicolumn %d:%d (%d,%d)",
361 				   i + 1, len,
362 				   win->_begy + y, win->_begx + x));
363 		line->text[x] = value;
364 		CHANGED_CELL(line, x);
365 		++x;
366 	    }
367 	    goto testwrapping;
368 	}
369     });
370 
371     /*
372      * Single-column characters.
373      */
374     line->text[x++] = ch;
375     /*
376      * This label is used only for wide-characters.
377      */
378     if_WIDEC(
379   testwrapping:
380     );
381 
382     TR(TRACE_VIRTPUT, ("cell (%ld, %ld..%d) = %s",
383 		       (long) win->_cury, (long) win->_curx, x - 1,
384 		       _tracech_t(CHREF(ch))));
385 
386     if (x > win->_maxx) {
387 	return wrap_to_next_line(win);
388     }
389     win->_curx = x;
390     return OK;
391 }
392 
393 static NCURSES_INLINE int
394 waddch_nosync(WINDOW *win, const NCURSES_CH_T ch)
395 /* the workhorse function -- add a character to the given window */
396 {
397     NCURSES_SIZE_T x, y;
398     chtype t = CharOf(ch);
399     const char *s = unctrl(t);
400 
401     /*
402      * If we are using the alternate character set, forget about locale.
403      * Otherwise, if unctrl() returns a single-character or the locale
404      * claims the code is printable, treat it that way.
405      */
406     if ((AttrOf(ch) & A_ALTCHARSET)
407 	|| (
408 #if USE_WIDEC_SUPPORT
409 	       (SP != 0 && SP->_legacy_coding) &&
410 #endif
411 	       s[1] == 0
412 	)
413 	|| (
414 	       isprint(t)
415 #if USE_WIDEC_SUPPORT
416 	       || ((SP == 0 || !SP->_legacy_coding) &&
417 		   (WINDOW_EXT(win, addch_used)
418 		    || !_nc_is_charable(CharOf(ch))))
419 #endif
420 	))
421 	return waddch_literal(win, ch);
422 
423     /*
424      * Handle carriage control and other codes that are not printable, or are
425      * known to expand to more than one character according to unctrl().
426      */
427     x = win->_curx;
428     y = win->_cury;
429 
430     switch (t) {
431     case '\t':
432 	x += (TABSIZE - (x % TABSIZE));
433 
434 	/*
435 	 * Space-fill the tab on the bottom line so that we'll get the
436 	 * "correct" cursor position.
437 	 */
438 	if ((!win->_scroll && (y == win->_regbottom))
439 	    || (x <= win->_maxx)) {
440 	    NCURSES_CH_T blank = blankchar;
441 	    AddAttr(blank, AttrOf(ch));
442 	    while (win->_curx < x) {
443 		if (waddch_literal(win, blank) == ERR)
444 		    return (ERR);
445 	    }
446 	    break;
447 	} else {
448 	    wclrtoeol(win);
449 	    win->_flags |= _WRAPPED;
450 	    if (newline_forces_scroll(win, &y)) {
451 		x = win->_maxx;
452 		if (win->_scroll) {
453 		    scroll(win);
454 		    x = 0;
455 		}
456 	    } else {
457 		x = 0;
458 	    }
459 	}
460 	break;
461     case '\n':
462 	wclrtoeol(win);
463 	if (newline_forces_scroll(win, &y)) {
464 	    if (win->_scroll)
465 		scroll(win);
466 	    else
467 		return (ERR);
468 	}
469 	/* FALLTHRU */
470     case '\r':
471 	x = 0;
472 	win->_flags &= ~_WRAPPED;
473 	break;
474     case '\b':
475 	if (x == 0)
476 	    return (OK);
477 	x--;
478 	win->_flags &= ~_WRAPPED;
479 	break;
480     default:
481 	while (*s) {
482 	    NCURSES_CH_T sch;
483 	    SetChar(sch, *s++, AttrOf(ch));
484 	    if_EXT_COLORS(SetPair(sch, GetPair(ch)));
485 	    if (waddch_literal(win, sch) == ERR)
486 		return ERR;
487 	}
488 	return (OK);
489     }
490 
491     win->_curx = x;
492     win->_cury = y;
493 
494     return (OK);
495 }
496 
497 NCURSES_EXPORT(int)
498 _nc_waddch_nosync(WINDOW *win, const NCURSES_CH_T c)
499 /* export copy of waddch_nosync() so the string-put functions can use it */
500 {
501     return (waddch_nosync(win, c));
502 }
503 
504 /*
505  * The versions below call _nc_synchook().  We wanted to avoid this in the
506  * version exported for string puts; they'll call _nc_synchook once at end
507  * of run.
508  */
509 
510 /* These are actual entry points */
511 
512 NCURSES_EXPORT(int)
513 waddch(WINDOW *win, const chtype ch)
514 {
515     int code = ERR;
516     NCURSES_CH_T wch;
517     SetChar2(wch, ch);
518 
519     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("waddch(%p, %s)"), win,
520 				      _tracechtype(ch)));
521 
522     if (win && (waddch_nosync(win, wch) != ERR)) {
523 	_nc_synchook(win);
524 	code = OK;
525     }
526 
527     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code));
528     return (code);
529 }
530 
531 NCURSES_EXPORT(int)
532 wechochar(WINDOW *win, const chtype ch)
533 {
534     int code = ERR;
535     NCURSES_CH_T wch;
536     SetChar2(wch, ch);
537 
538     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("wechochar(%p, %s)"), win,
539 				      _tracechtype(ch)));
540 
541     if (win && (waddch_nosync(win, wch) != ERR)) {
542 	bool save_immed = win->_immed;
543 	win->_immed = TRUE;
544 	_nc_synchook(win);
545 	win->_immed = save_immed;
546 	code = OK;
547     }
548     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code));
549     return (code);
550 }
551