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