ncurses 5.7 - patch 20091024
[ncurses.git] / ncurses / widechar / lib_add_wch.c
1 /****************************************************************************
2  * Copyright (c) 2004-2006,2009 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_add_wch.c
31 **
32 **      The routine wadd_wch().
33 **
34 */
35
36 #include <curses.priv.h>
37
38 #if HAVE_WCTYPE_H
39 #include <wctype.h>
40 #endif
41
42 MODULE_ID("$Id: lib_add_wch.c,v 1.9 2009/10/24 22:45:42 tom Exp $")
43
44 /* clone/adapt lib_addch.c */
45 static const cchar_t blankchar = NewChar(BLANK_TEXT);
46
47 /*
48  * Ugly microtweaking alert.  Everything from here to end of module is
49  * likely to be speed-critical -- profiling data sure says it is!
50  * Most of the important screen-painting functions are shells around
51  * wadd_wch().  So we make every effort to reduce function-call overhead
52  * by inlining stuff, even at the cost of making wrapped copies for
53  * export.  Also we supply some internal versions that don't call the
54  * window sync hook, for use by string-put functions.
55  */
56
57 /* Return bit mask for clearing color pair number if given ch has color */
58 #define COLOR_MASK(ch) (~(attr_t)((ch) & A_COLOR ? A_COLOR : 0))
59
60 static NCURSES_INLINE cchar_t
61 render_char(WINDOW *win, cchar_t ch)
62 /* compute a rendition of the given char correct for the current context */
63 {
64     attr_t a = WINDOW_ATTRS(win);
65     int pair = GetPair(ch);
66
67     if (ISBLANK(ch)
68         && AttrOf(ch) == A_NORMAL
69         && pair == 0) {
70         /* color/pair in attrs has precedence over bkgrnd */
71         ch = win->_nc_bkgd;
72         SetAttr(ch, a | AttrOf(win->_nc_bkgd));
73         if ((pair = GET_WINDOW_PAIR(win)) == 0)
74             pair = GetPair(win->_nc_bkgd);
75         SetPair(ch, pair);
76     } else {
77         /* color in attrs has precedence over bkgrnd */
78         a |= AttrOf(win->_nc_bkgd) & COLOR_MASK(a);
79         /* color in ch has precedence */
80         if (pair == 0) {
81             if ((pair = GET_WINDOW_PAIR(win)) == 0)
82                 pair = GetPair(win->_nc_bkgd);
83         }
84 #if 0
85         if (pair > 255) {
86             cchar_t fixme = ch;
87             SetPair(fixme, pair);
88         }
89 #endif
90         AddAttr(ch, (a & COLOR_MASK(AttrOf(ch))));
91         SetPair(ch, pair);
92     }
93
94     TR(TRACE_VIRTPUT,
95        ("render_char bkg %s (%d), attrs %s (%d) -> ch %s (%d)",
96         _tracech_t2(1, CHREF(win->_nc_bkgd)),
97         GetPair(win->_nc_bkgd),
98         _traceattr(WINDOW_ATTRS(win)),
99         GET_WINDOW_PAIR(win),
100         _tracech_t2(3, CHREF(ch)),
101         GetPair(ch)));
102
103     return (ch);
104 }
105
106 /* check if position is legal; if not, return error */
107 #ifndef NDEBUG                  /* treat this like an assertion */
108 #define CHECK_POSITION(win, x, y) \
109         if (y > win->_maxy \
110          || x > win->_maxx \
111          || y < 0 \
112          || x < 0) { \
113                 TR(TRACE_VIRTPUT, ("Alert! Win=%p _curx = %d, _cury = %d " \
114                                    "(_maxx = %d, _maxy = %d)", win, x, y, \
115                                    win->_maxx, win->_maxy)); \
116                 return(ERR); \
117         }
118 #else
119 #define CHECK_POSITION(win, x, y)       /* nothing */
120 #endif
121
122 static bool
123 newline_forces_scroll(WINDOW *win, NCURSES_SIZE_T * ypos)
124 {
125     bool result = FALSE;
126
127     if (*ypos >= win->_regtop && *ypos == win->_regbottom) {
128         *ypos = win->_regbottom;
129         result = TRUE;
130     } else {
131         *ypos += 1;
132     }
133     return result;
134 }
135
136 /*
137  * The _WRAPPED flag is useful only for telling an application that we've just
138  * wrapped the cursor.  We don't do anything with this flag except set it when
139  * wrapping, and clear it whenever we move the cursor.  If we try to wrap at
140  * the lower-right corner of a window, we cannot move the cursor (since that
141  * wouldn't be legal).  So we return an error (which is what SVr4 does). 
142  * Unlike SVr4, we can successfully add a character to the lower-right corner
143  * (Solaris 2.6 does this also, however).
144  */
145 static int
146 wrap_to_next_line(WINDOW *win)
147 {
148     win->_flags |= _WRAPPED;
149     if (newline_forces_scroll(win, &(win->_cury))) {
150         win->_curx = win->_maxx;
151         if (!win->_scroll)
152             return (ERR);
153         scroll(win);
154     }
155     win->_curx = 0;
156     return (OK);
157 }
158
159 static int wadd_wch_literal(WINDOW *, cchar_t);
160 /*
161  * Fill the given number of cells with blanks using the current background
162  * rendition.  This saves/restores the current x-position.
163  */
164 static void
165 fill_cells(WINDOW *win, int count)
166 {
167     cchar_t blank = blankchar;
168     int save_x = win->_curx;
169     int save_y = win->_cury;
170
171     while (count-- > 0) {
172         if (wadd_wch_literal(win, blank) == ERR)
173             break;
174     }
175     win->_curx = save_x;
176     win->_cury = save_y;
177 }
178
179 static int
180 wadd_wch_literal(WINDOW *win, cchar_t ch)
181 {
182     int x;
183     int y;
184     struct ldat *line;
185
186     x = win->_curx;
187     y = win->_cury;
188
189     CHECK_POSITION(win, x, y);
190
191     ch = render_char(win, ch);
192
193     line = win->_line + y;
194
195     CHANGED_CELL(line, x);
196
197     /*
198      * Non-spacing characters are added to the current cell.
199      *
200      * Spacing characters that are wider than one column require some display
201      * adjustments.
202      */
203     {
204         int len = wcwidth(CharOf(ch));
205         int i;
206         int j;
207         wchar_t *chars;
208
209         if (len == 0) {         /* non-spacing */
210             if ((x > 0 && y >= 0)
211                 || (win->_maxx >= 0 && win->_cury >= 1)) {
212                 if (x > 0 && y >= 0)
213                     chars = (win->_line[y].text[x - 1].chars);
214                 else
215                     chars = (win->_line[y - 1].text[win->_maxx].chars);
216                 for (i = 0; i < CCHARW_MAX; ++i) {
217                     if (chars[i] == 0) {
218                         TR(TRACE_VIRTPUT,
219                            ("added non-spacing %d: %x",
220                             x, (int) CharOf(ch)));
221                         chars[i] = CharOf(ch);
222                         break;
223                     }
224                 }
225             }
226             goto testwrapping;
227         } else if (len > 1) {   /* multi-column characters */
228             /*
229              * Check if the character will fit on the current line.  If it does
230              * not fit, fill in the remainder of the line with blanks.  and
231              * move to the next line.
232              */
233             if (len > win->_maxx + 1) {
234                 TR(TRACE_VIRTPUT, ("character will not fit"));
235                 return ERR;
236             } else if (x + len > win->_maxx + 1) {
237                 int count = win->_maxx + 1 - x;
238                 TR(TRACE_VIRTPUT, ("fill %d remaining cells", count));
239                 fill_cells(win, count);
240                 if (wrap_to_next_line(win) == ERR)
241                     return ERR;
242                 x = win->_curx;
243                 y = win->_cury;
244                 line = win->_line + y;
245             }
246             /*
247              * Check for cells which are orphaned by adding this character, set
248              * those to blanks.
249              *
250              * FIXME: this actually could fill j-i cells, more complicated to
251              * setup though.
252              */
253             for (i = 0; i < len; ++i) {
254                 if (isWidecBase(win->_line[y].text[x + i])) {
255                     break;
256                 } else if (isWidecExt(win->_line[y].text[x + i])) {
257                     for (j = i; x + j <= win->_maxx; ++j) {
258                         if (!isWidecExt(win->_line[y].text[x + j])) {
259                             TR(TRACE_VIRTPUT, ("fill %d orphan cells", j));
260                             fill_cells(win, j);
261                             break;
262                         }
263                     }
264                     break;
265                 }
266             }
267             /*
268              * Finally, add the cells for this character.
269              */
270             for (i = 0; i < len; ++i) {
271                 cchar_t value = ch;
272                 SetWidecExt(value, i);
273                 TR(TRACE_VIRTPUT, ("multicolumn %d:%d (%d,%d)",
274                                    i + 1, len,
275                                    win->_begy + y, win->_begx + x));
276                 line->text[x] = value;
277                 CHANGED_CELL(line, x);
278                 ++x;
279             }
280             goto testwrapping;
281         }
282     }
283
284     /*
285      * Single-column characters.
286      */
287     line->text[x++] = ch;
288     /*
289      * This label is used only for wide-characters.
290      */
291   testwrapping:
292
293     TR(TRACE_VIRTPUT, ("cell (%ld, %ld..%d) = %s",
294                        (long) win->_cury, (long) win->_curx, x - 1,
295                        _tracech_t(CHREF(ch))));
296
297     if (x > win->_maxx) {
298         return wrap_to_next_line(win);
299     }
300     win->_curx = x;
301     return OK;
302 }
303
304 static NCURSES_INLINE int
305 wadd_wch_nosync(WINDOW *win, cchar_t ch)
306 /* the workhorse function -- add a character to the given window */
307 {
308     NCURSES_SIZE_T x, y;
309     wchar_t *s;
310     int tabsize = 8;
311 #if USE_REENTRANT
312     SCREEN *sp = _nc_screen_of(win);
313 #endif
314
315     /*
316      * If we are using the alternate character set, forget about locale.
317      * Otherwise, if the locale * claims the code is printable, treat it that
318      * way.
319      */
320     if ((AttrOf(ch) & A_ALTCHARSET)
321         || iswprint(CharOf(ch)))
322         return wadd_wch_literal(win, ch);
323
324     /*
325      * Handle carriage control and other codes that are not printable, or are
326      * known to expand to more than one character according to unctrl().
327      */
328     x = win->_curx;
329     y = win->_cury;
330
331     switch (CharOf(ch)) {
332     case '\t':
333 #if USE_REENTRANT
334         tabsize = *ptrTabsize(sp);
335 #else
336         tabsize = TABSIZE;
337 #endif
338         x += (tabsize - (x % tabsize));
339         /*
340          * Space-fill the tab on the bottom line so that we'll get the
341          * "correct" cursor position.
342          */
343         if ((!win->_scroll && (y == win->_regbottom))
344             || (x <= win->_maxx)) {
345             cchar_t blank = blankchar;
346             AddAttr(blank, AttrOf(ch));
347             while (win->_curx < x) {
348                 if (wadd_wch_literal(win, blank) == ERR)
349                     return (ERR);
350             }
351             break;
352         } else {
353             wclrtoeol(win);
354             win->_flags |= _WRAPPED;
355             if (newline_forces_scroll(win, &y)) {
356                 x = win->_maxx;
357                 if (win->_scroll) {
358                     scroll(win);
359                     x = 0;
360                 }
361             } else {
362                 x = 0;
363             }
364         }
365         break;
366     case '\n':
367         wclrtoeol(win);
368         if (newline_forces_scroll(win, &y)) {
369             if (win->_scroll)
370                 scroll(win);
371             else
372                 return (ERR);
373         }
374         /* FALLTHRU */
375     case '\r':
376         x = 0;
377         win->_flags &= ~_WRAPPED;
378         break;
379     case '\b':
380         if (x == 0)
381             return (OK);
382         x--;
383         win->_flags &= ~_WRAPPED;
384         break;
385     default:
386         if ((s = wunctrl(&ch)) != 0) {
387             while (*s) {
388                 cchar_t sch;
389                 SetChar(sch, *s++, AttrOf(ch));
390                 if_EXT_COLORS(SetPair(sch, GetPair(ch)));
391                 if (wadd_wch_literal(win, sch) == ERR)
392                     return ERR;
393             }
394             return OK;
395         }
396         return ERR;
397     }
398
399     win->_curx = x;
400     win->_cury = y;
401
402     return OK;
403 }
404
405 /*
406  * The versions below call _nc_synchook().  We wanted to avoid this in the
407  * version exported for string puts; they'll call _nc_synchook once at end
408  * of run.
409  */
410
411 /* These are actual entry points */
412
413 NCURSES_EXPORT(int)
414 wadd_wch(WINDOW *win, const cchar_t *wch)
415 {
416     int code = ERR;
417
418     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("wadd_wch(%p, %s)"),
419                                       (void *) win,
420                                       _tracecchar_t(wch)));
421
422     if (win && (wadd_wch_nosync(win, *wch) != ERR)) {
423         _nc_synchook(win);
424         code = OK;
425     }
426
427     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code));
428     return (code);
429 }
430
431 NCURSES_EXPORT(int)
432 wecho_wchar(WINDOW *win, const cchar_t *wch)
433 {
434     int code = ERR;
435
436     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("wechochar(%p, %s)"),
437                                       (void *) win,
438                                       _tracecchar_t(wch)));
439
440     if (win && (wadd_wch_nosync(win, *wch) != ERR)) {
441         bool save_immed = win->_immed;
442         win->_immed = TRUE;
443         _nc_synchook(win);
444         win->_immed = save_immed;
445         code = OK;
446     }
447     TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code));
448     return (code);
449 }