1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * m_cc.c 29 * 30 * XCurses Library 31 * 32 * Copyright 1990, 1995 by Mortice Kern Systems Inc. All rights reserved. 33 * 34 */ 35 36 #if M_RCSID 37 #ifndef lint 38 static char rcsID[] = "$Header: /rd/src/libc/xcurses/rcs/m_cc.c 1.8 1995/09/20 15:26:52 ant Exp $"; 39 #endif 40 #endif 41 42 #include <private.h> 43 #include <errno.h> 44 #include <limits.h> 45 #include <string.h> 46 #include <m_wio.h> 47 48 typedef struct { 49 int max; 50 int used; 51 char *mbs; 52 } t_string; 53 54 static int 55 write_string(byte, sp) 56 int byte; 57 t_string *sp; 58 { 59 if (sp->max <= sp->used) 60 return EOF; 61 62 sp->mbs[sp->used++] = byte; 63 64 return byte; 65 } 66 67 /* 68 * Convert a wint_t string into a multibyte string. 69 * 70 * The conversion stops at the end of string or the first WEOF. 71 * Return the number of bytes successfully placed into mbs. 72 */ 73 int 74 wistombs(mbs, wis, n) 75 char *mbs; 76 const wint_t *wis; 77 int n; 78 { 79 int last; 80 t_string string = { 0 }; 81 t_wide_io convert = { 0 }; 82 83 string.max = n; 84 string.mbs = mbs; 85 convert.object = (void *) &string; 86 convert.put = (int (*)(int, void *)) write_string; 87 88 for (;; ++wis) { 89 /* In case of error, rewind string to the last character. */ 90 last = string.used; 91 92 if (m_wio_put(*wis, &convert) < 0) { 93 string.used = last; 94 break; 95 } 96 97 /* Test for end of string AFTER trying to copy into the 98 * buffer, because m_wio_put() has to handle state changes 99 * back to the initial state on '\0' or WEOF. 100 */ 101 if (*wis == '\0' || *wis == WEOF) 102 break; 103 } 104 105 /* m_wio_put() does not write '\0', because the "stream" 106 * object is considered to be in "text" mode, which in the 107 * case of file I/O produces undefined results for systems 108 * using locking-shift character sets. 109 */ 110 string.mbs[string.used] = '\0'; 111 112 return string.used; 113 } 114 115 /* 116 * Convert a wint_t string (filled in by wgetn_wstr()) to a wchar_t string. 117 * The conversion stops at the end of string or the first WEOF. Return the 118 * number of successfully copied characters. 119 * 120 * This routinue should be used when sizeof (wchar_t) < sizeof (wint_t). 121 */ 122 int 123 wistowcs(wcs, wis, n) 124 wchar_t *wcs; 125 const wint_t *wis; 126 int n; 127 { 128 wchar_t *start; 129 130 if (n < 0) 131 n = INT_MAX; 132 133 for (start = wcs; *wis != '\0' && 0 < n; ++wis, ++wcs, --n) { 134 if (*wis == WEOF) 135 break; 136 *wcs = (wchar_t) *wis; 137 } 138 *wcs = '\0'; 139 140 return (int) (wcs - start); 141 } 142 143 /* 144 * Convert a chtype to a cchar_t. 145 */ 146 int 147 __m_chtype_cc(ch, cc) 148 chtype ch; 149 cchar_t *cc; 150 { 151 char mb; 152 153 cc->_f = 1; 154 cc->_n = 1; 155 mb = (char)(ch & A_CHARTEXT); 156 157 if (mbtowc(cc->_wc, &mb, 1) < 0) 158 return ERR; 159 160 cc->_co = (short) PAIR_NUMBER(ch); 161 cc->_at = (attr_t) ((ch & (A_ATTRIBUTES & ~A_COLOR)) >> 16); 162 163 return OK; 164 } 165 166 /* 167 * Return a complex character as a chtype. 168 */ 169 chtype 170 __m_cc_chtype(cc) 171 const cchar_t *cc; 172 { 173 chtype ch; 174 unsigned char mb[MB_LEN_MAX]; 175 176 /* Is it a single-byte character? */ 177 if (cc->_n != 1 || wctomb((char *) mb, cc->_wc[0]) != 1) 178 return (chtype) ERR; 179 180 ch = ((chtype) cc->_at << 16) & ~A_COLOR; 181 ch |= COLOR_PAIR(cc->_co) | mb[0]; 182 183 return ch; 184 } 185 186 /* 187 * Convert a complex character's "character" into a multibyte string. 188 * The attribute and colour are ignored. 189 * 190 * If 0 < n, set a new multibyte string and convert the first character, 191 * returning either -1 on error or the number of bytes used to convert the 192 * character. 193 * 194 * If n == 0, continue appending to the current multibyte string and return 195 * a value as for 0 < n case. 196 * 197 * If n < 0, return the accumulated byte length of the current multibyte 198 * string and do nothing else. 199 * 200 * When converting a character, a null cchar_t pointer will force the initial 201 * shift state and append a '\0' to the multibyte string. The return value 202 * will instead by the number of bytes used to shift to the initial state, 203 * and exclude the '\0'. 204 */ 205 int 206 __m_cc_mbs(cc, mbs, n) 207 const cchar_t *cc; 208 char *mbs; 209 int n; 210 { 211 cchar_t *cp; 212 int i, bytes, count, last; 213 mbstate_t initial = { 0 }; 214 static t_string string = { 0 }; 215 static t_wide_io convert = { 0 }; 216 217 if (n < 0) { 218 /* Return total number of bytes written to multibyte string. */ 219 return string.used; 220 } else if (0 < n) { 221 /* Start a new conversion. */ 222 string.max = n; 223 string.used = 0; 224 string.mbs = mbs; 225 226 convert._state = initial; 227 convert._next = convert._size = 0; 228 convert.object = (void *) &string; 229 convert.put = (int (*)(int, void *)) write_string; 230 } /* else n == 0, continue appending to previous mbs. */ 231 232 /* In case of error, rewind string to the last character. */ 233 last = string.used; 234 235 if (cc == (cchar_t *) 0) { 236 /* Force initial shift state. */ 237 if ((count = m_wio_put('\0', &convert)) < 0) { 238 string.used = last; 239 return -1; 240 } 241 242 if (string.used < string.max) 243 string.mbs[string.used++] = '\0'; 244 } else { 245 for (count = i = 0; i < cc->_n; ++i, count += bytes) 246 if ((bytes = m_wio_put(cc->_wc[i], &convert)) < 0) { 247 string.used = last; 248 return -1; 249 } 250 } 251 252 return count; 253 } 254 255 /* 256 * Convert a stty character into a wchar_t. 257 */ 258 int 259 __m_tty_wc(index, wcp) 260 int index; 261 wchar_t *wcp; 262 { 263 char mb; 264 int code; 265 266 /* Refer to _shell instead of _prog, since _shell will 267 * correctly reflect the user's prefered settings, whereas 268 * _prog may not have been initialised if both input and 269 * output have been redirected. 270 */ 271 mb = cur_term->_shell.c_cc[index]; 272 code = mbtowc(wcp, &mb, 1) < 0 ? ERR : OK; 273 274 return code; 275 } 276 277 /* 278 * Build a cchar_t from the leading spacing and non-spacing characters 279 * in the multibyte character string. Only one spacing character is copied 280 * from the multibyte character string. 281 * 282 * Return the number of characters copied from the string, or -1 on error. 283 */ 284 int 285 __m_mbs_cc(const char *mbs, attr_t at, short co, cchar_t *cc) 286 { 287 wchar_t wc; 288 const char *start; 289 int i, nbytes, width, have_one; 290 291 for (start = mbs, have_one = i = 0; *mbs != '\0'; mbs += nbytes, ++i) { 292 if (sizeof cc->_wc <= i) 293 /* Too many characters. */ 294 return -1; 295 296 if ((nbytes = mbtowc(&wc, mbs, UINT_MAX)) < 0) 297 /* Invalid multibyte sequence. */ 298 return -1; 299 300 if (nbytes == 0) 301 /* Remainder of string evaluates to the null byte. */ 302 break; 303 304 if (iscntrl(*mbs)) 305 /* Treat control codes like a spacing character. */ 306 width = 1; 307 else if ((width = wcwidth(wc)) < 0) 308 return -1; 309 310 /* Do we have a spacing character? */ 311 if (0 < width) { 312 if (have_one) 313 break; 314 have_one = 1; 315 } 316 317 cc->_wc[i] = wc; 318 } 319 320 cc->_f = 1; 321 cc->_n = i; 322 cc->_co = co; 323 cc->_at = at; 324 325 (void) __m_cc_sort(cc); 326 327 return (int) (mbs - start); 328 } 329 330 /* 331 * Build a cchar_t from the leading spacing and non-spacing characters 332 * in the wide character string. Only one spacinig character is copied 333 * from the wide character string. 334 * 335 * Return the number of characters copied from the string, or -1 on error. 336 */ 337 int 338 __m_wcs_cc(const wchar_t *wcs, attr_t at, short co, cchar_t *cc) 339 { 340 short i; 341 int width, have_one; 342 const wchar_t *start; 343 344 for (start = wcs, have_one = i = 0; *wcs != '\0'; ++wcs, ++i) { 345 if (sizeof cc->_wc <= i) 346 /* Too many characters. */ 347 return -1; 348 349 if ((width = wcwidth(*wcs)) < 0) 350 return -1; 351 352 if (0 < width) { 353 if (have_one) 354 break; 355 have_one = 1; 356 } 357 358 cc->_wc[i] = *wcs; 359 } 360 361 cc->_f = 1; 362 cc->_n = i; 363 cc->_co = co; 364 cc->_at = at; 365 366 (void) __m_cc_sort(cc); 367 368 return (int) (wcs - start); 369 } 370 371 /* 372 * Convert a single wide character into a complex character. 373 */ 374 int 375 __m_wc_cc(wint_t wc, cchar_t *cc) 376 { 377 wchar_t wcs[2]; 378 379 if (wc == WEOF) 380 return -1; 381 382 wcs[0] = (wchar_t)wc; 383 wcs[1] = '\0'; 384 (void) __m_wcs_cc(wcs, WA_NORMAL, 0, cc); 385 386 return 0; 387 } 388 389 /* 390 * Sort a complex character into a spacing character followed 391 * by any non-spacing characters in increasing order of oridinal 392 * values. This facilitates both comparision and writting of 393 * complex characters. More than one spacing character is 394 * considered an error. 395 * 396 * Return the spacing character's column width or -1 if more 397 * than one spacing character appears in cc. 398 */ 399 int 400 __m_cc_sort(cc) 401 cchar_t *cc; 402 { 403 wchar_t wc; 404 int width, i, j, spacing; 405 406 /* Find spacing character and place in as first element. */ 407 for (width = spacing = i = 0; i < cc->_n; ++i) { 408 j = wcwidth(cc->_wc[i]); 409 if (0 < j) { 410 /* More than one spacing character is an error. */ 411 if (0 < width) 412 return -1; 413 414 wc = cc->_wc[0]; 415 cc->_wc[0] = cc->_wc[i]; 416 cc->_wc[i] = wc; 417 418 spacing = 1; 419 width = j; 420 break; 421 } 422 } 423 424 /* Bubble sort small array. */ 425 for (i = spacing; i < cc->_n; ++i) { 426 for (j = cc->_n - 1; i < j; --j) { 427 if (cc->_wc[j-1] > cc->_wc[j]) { 428 wc = cc->_wc[j]; 429 cc->_wc[j] = cc->_wc[j-1]; 430 cc->_wc[j-1] = wc; 431 } 432 } 433 } 434 435 return width; 436 } 437 438 /* 439 * Return width inn screen columns of the character. 440 */ 441 int 442 __m_cc_width(cc) 443 const cchar_t *cc; 444 { 445 return wcwidth(cc->_wc[0]); 446 } 447 448 /* 449 * Return the first column of a multi-column character, in window. 450 */ 451 int 452 __m_cc_first(w, y, x) 453 WINDOW *w; 454 int y, x; 455 { 456 register cchar_t *lp; 457 458 for (lp = w->_line[y]; 0 < x; --x) { 459 if (lp[x]._f) 460 break; 461 } 462 463 return x; 464 } 465 466 /* 467 * Return the start of the next multi-column character, in window. 468 */ 469 int 470 __m_cc_next(w, y, x) 471 WINDOW *w; 472 int y, x; 473 { 474 cchar_t *lp; 475 476 for (lp = w->_line[y]; ++x < w->_maxx; ) { 477 if (lp[x]._f) 478 break; 479 } 480 481 return x; 482 } 483 484 /* 485 * Return true if valid last column of a multi-column character. 486 */ 487 int 488 __m_cc_islast(w, y, x) 489 WINDOW *w; 490 int y, x; 491 { 492 int first, width; 493 494 first = __m_cc_first(w, y, x); 495 width = __m_cc_width(&w->_line[y][x]); 496 497 return first + width == x + 1; 498 } 499 500 /* 501 * Replace the character at the current cursor location 502 * according to the column width of the character. The 503 * cursor does not advance. 504 * 505 * Return -1 if the character won't fit on the line and the background 506 * was written in its place; else return the width of the character in 507 * screen columns. 508 */ 509 int 510 __m_cc_replace(w, y, x, cc, as_is) 511 WINDOW *w; 512 int y, x; 513 const cchar_t *cc; 514 int as_is; 515 { 516 int i, width; 517 cchar_t *cp, *np; 518 519 width = __m_cc_width(cc); 520 521 /* If we try to write a broad character that would exceed the 522 * right margin, then write the background character instead. 523 */ 524 if (0 < width && w->_maxx < x + width) { 525 (void) __m_cc_erase(w, y, x, y, w->_maxx-1); 526 return -1; 527 } 528 529 /* Erase the region to be occupied by the new character. 530 * __m_cc_erase() will erase whole characters so that 531 * writing a multicolumn character that overwrites the 532 * trailing and leading portions of two already existing 533 * multicolumn characters, erases the remaining portions. 534 */ 535 (void) __m_cc_erase(w, y, x, y, x + width - 1); 536 537 /* Write the first column of the character. */ 538 cp = &w->_line[y][x++]; 539 if (cc->_wc[0] == ' ' || cc->_wc[0] == M_MB_L(' ')) { 540 *cp = w->_bg; 541 cp->_at |= cc->_at; 542 if (cc->_co != 0) 543 cp->_co = cc->_co; 544 } else { 545 (void) __m_wacs_cc(cc, cp); 546 if (cc->_co == 0) 547 cp->_co = w->_fg._co; 548 } 549 550 cp->_at |= w->_fg._at | w->_bg._at; 551 552 /* Mark this as the first column of the character. */ 553 cp->_f = 1; 554 555 /* Duplicate the character in every column the character occupies. */ 556 for (np = cp + 1, i = 1; i < width; ++i, ++x, ++np) { 557 *np = *cp; 558 np->_f = 0; 559 } 560 561 return width; 562 } 563 564 int 565 __m_do_scroll(WINDOW *w, int y, int x, int *yp, int *xp) 566 { 567 if (w->_maxx <= x) 568 x = w->_maxx-1; 569 570 ++y; 571 572 if (y == w->_bottom) { 573 --y; 574 if (w->_flags & W_CAN_SCROLL) { 575 if (wscrl(w, 1) == ERR) 576 return ERR; 577 x = 0; 578 } 579 } else if (w->_maxy <= y) { 580 y = w->_maxy-1; 581 } else { 582 /* The cursor wraps for any line (in and out of the scroll 583 * region) except for the last line of the scroll region. 584 */ 585 x = 0; 586 } 587 588 *yp = y; 589 *xp = x; 590 591 return OK; 592 } 593 594 /* 595 * Add the character at the current cursor location 596 * according to the column width of the character. 597 * The cursor will be advanced. 598 * 599 * Return ERR if adding the character causes the 600 * screen to scroll, when it is disallowed. 601 */ 602 int 603 __m_cc_add(w, y, x, cc, as_is, yp, xp) 604 WINDOW *w; 605 int y, x; 606 const cchar_t *cc; 607 int as_is, *yp, *xp; 608 { 609 int nx, width, code = ERR; 610 611 #ifdef M_CURSES_TRACE 612 __m_trace( 613 "__m_cc_add(%p, %d, %d, %p, %d, %p, %p)", 614 w, y, x, cc, as_is, yp, xp 615 ); 616 #endif 617 618 switch (cc->_wc[0]) { 619 case '\t': 620 nx = x + (8 - (x & 07)); 621 if (__m_cc_erase(w, y, x, y, nx-1) == -1) 622 goto error; 623 x = nx; 624 625 if (w->_maxx <= x) { 626 if (__m_do_scroll(w, y, x, &y, &x) == ERR) 627 goto error; 628 } 629 break; 630 case '\n': 631 if (__m_cc_erase(w, y, x, y, w->_maxx-1) == -1) 632 goto error; 633 634 if (__m_do_scroll(w, y, x, &y, &x) == ERR) 635 goto error; 636 break; 637 case '\r': 638 x = 0; 639 break; 640 case '\b': 641 if (0 < x) 642 --x; 643 break; 644 default: 645 width = __m_cc_replace(w, y, x, cc, as_is); 646 647 x += width; 648 649 if (width < 0 || w->_maxx <= x) { 650 if (__m_do_scroll(w, y, x, &y, &x) == ERR) 651 goto error; 652 653 if (width < 0) 654 x += __m_cc_replace(w, y, x, cc, as_is); 655 } 656 } 657 658 code = OK; 659 error: 660 *yp = y; 661 *xp = x; 662 663 return __m_return_code("__m_cc_add", code); 664 } 665 666 /* 667 * Erase region from (y,x) to (ly, lx) inclusive. The 668 * region is extended left and right in the case where 669 * the portions of a multicolumn characters are erased. 670 * 671 * Return -1 if the region is not an integral multiple 672 * of the background character, else zero for success. 673 */ 674 int 675 __m_cc_erase(w, y, x, ly, lx) 676 WINDOW *w; 677 int y, x, ly, lx; 678 { 679 cchar_t *cp; 680 int i, width; 681 682 if (ly < y) 683 return -1; 684 685 if (w->_maxy <= ly) 686 ly = w->_maxy - 1; 687 if (w->_maxx <= lx) 688 lx = w->_maxx - 1; 689 690 /* Erase from first whole character (inclusive) to next 691 * character (exclusive). 692 */ 693 x = __m_cc_first(w, y, x); 694 lx = __m_cc_next(w, ly, lx) - 1; 695 696 /* Is the region to blank out an integral width of the 697 * background character? 698 */ 699 width = __m_cc_width(&w->_bg); 700 701 if (y < ly && (lx + 1) % width != 0) 702 return -1; 703 if ((lx - x + 1) % width != 0) 704 return -1; 705 706 for (; y < ly; ++y, x = 0) { 707 if (x < w->_first[y]) 708 w->_first[y] = (short) x; 709 710 for (cp = w->_line[y], i = 0; x < w->_maxx; ++x, ++i) { 711 cp[x] = w->_bg; 712 713 /* The start of each new character will be set true 714 * while internal columns of the character will be 715 * reset to false. 716 */ 717 cp[x]._f = (short) (i % width == 0); 718 } 719 720 if (w->_last[y] < x) 721 w->_last[y] = (short) x; 722 } 723 724 if (x < w->_first[y]) 725 w->_first[y] = (short) x; 726 727 for (cp = w->_line[y], i = 0; x <= lx; ++x, ++i) { 728 cp[x] = w->_bg; 729 730 /* The start of each new character will be set true 731 * while internal columns of the character will be 732 * reset to false. 733 */ 734 cp[x]._f = (short) (i % width == 0); 735 } 736 737 if (w->_last[y] < x) 738 w->_last[y] = (short) x; 739 740 return 0; 741 } 742 743 /* 744 * Expand the character to the left or right of the given position. 745 * Return the value returned by __m_cc_replace(). 746 */ 747 int 748 __m_cc_expand(w, y, x, side) 749 WINDOW *w; 750 int y, x, side; 751 { 752 cchar_t cc; 753 int dx, width; 754 755 width = __m_cc_width(&w->_line[y][x]); 756 757 if (side < 0) 758 dx = __m_cc_next(w, y, x) - width; 759 else if (0 < side) 760 dx = __m_cc_first(w, y, x); 761 else 762 return -1; 763 764 /* __m_cc_replace() will erase the region containing 765 * the character we want to expand. 766 */ 767 cc = w->_line[y][x]; 768 769 return __m_cc_replace(w, y, dx, &cc, 0); 770 } 771 772 /* 773 * Return true if characters are equal. 774 * 775 * NOTE to guarantee correct results, make sure that both 776 * characters have been passed through __m_cc_sort(). 777 */ 778 int 779 __m_cc_compare(c1, c2, exact) 780 const cchar_t *c1, *c2; 781 int exact; 782 { 783 int i; 784 785 if (exact && c1->_f != c2->_f) 786 return 0; 787 if (c1->_n != c2->_n) 788 return 0; 789 if ((c1->_at & ~WA_COOKIE) != (c2->_at & ~WA_COOKIE)) 790 return 0; 791 if (c1->_co != c2->_co) 792 return 0; 793 794 for (i = 0; i < c1->_n; ++i) 795 if (c1->_wc[i] != c2->_wc[i]) 796 return 0; 797 798 return 1; 799 } 800 801 /* 802 * Write to the stream the character portion of a cchar_t. 803 */ 804 int 805 __m_cc_write(cc) 806 const cchar_t *cc; 807 { 808 size_t i, j; 809 char mb[MB_LEN_MAX]; 810 811 errno = 0; 812 for (i = 0; i < cc->_n; ++i) { 813 j = wcrtomb(mb, cc->_wc[i], &__m_screen->_state); 814 if (errno != 0) 815 return EOF; 816 if (fwrite(mb, sizeof *mb, j, __m_screen->_of) == 0) 817 return EOF; 818 } 819 820 return 0; 821 } 822