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