1/* $NetBSD: msg_sys.def,v 1.31 2004/11/05 20:45:48 dsl Exp $ */ 2 3/* 4 * Copyright 1997 Piermont Information Systems Inc. 5 * All rights reserved. 6 * 7 * Written by Philip A. Nelson for Piermont Information Systems Inc. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software develooped for the NetBSD Project by 20 * Piermont Information Systems Inc. 21 * 4. The name of Piermont Information Systems Inc. may not be used to endorse 22 * or promote products derived from this software without specific prior 23 * written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY PIERMONT INFORMATION SYSTEMS INC. ``AS IS'' 26 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL PIERMONT INFORMATION SYSTEMS INC. BE 29 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 35 * THE POSSIBILITY OF SUCH DAMAGE. 36 * 37 */ 38 39static WINDOW *msg_win = NULL; 40static char *cbuffer; 41static size_t cbuffersize; 42 43static int last_i_was_nl, last_i_was_space; 44static int last_o_was_punct, last_o_was_space; 45 46static void _msg_beep(void); 47static int _msg_vprintf(int, const char *, va_list); 48#define MSG_PROMPT_ECHO 1 49#define MSG_PROMPT_HIDE_DFLT 2 50static void _msg_vprompt(const char *, int, const char *, char *, 51 size_t, va_list); 52 53static char *msgmap = MAP_FAILED; 54static size_t msgmapsz; 55static int msgmapcount; 56 57/* Routines */ 58 59static void 60_msg_beep(void) 61{ 62 63 fprintf(stderr, "\a"); 64} 65 66WINDOW * 67msg_window(WINDOW *window) 68{ 69 size_t ncbuffersize; 70 char *ncbuffer; 71 WINDOW *old; 72 73 old = msg_win; 74 if (!window) 75 return old; 76 msg_win = window; 77 78 ncbuffersize = getmaxx(window) * getmaxy(window) + 1; 79 while (ncbuffersize > cbuffersize) { 80 ncbuffer = malloc(ncbuffersize); 81 if (ncbuffer == NULL) { 82 /* we might get truncated messages... */ 83 ncbuffersize <<= 1; 84 continue; 85 } 86 if (cbuffer != NULL) 87 free(cbuffer); 88 cbuffer = ncbuffer; 89 cbuffersize = ncbuffersize; 90 break; 91 } 92 last_o_was_punct = 0; 93 last_o_was_space = 1; 94 return old; 95} 96 97int 98msg_file(const char *file) 99{ 100 int fd; 101 102 if (msgmap != MAP_FAILED) 103 munmap(msgmap, msgmapsz); 104 msgmap = MAP_FAILED; 105 if (!file) 106 return 0; 107 fd = open(file, O_RDONLY, 0); 108 if (fd == -1) 109 return -1; 110 msgmapsz = lseek(fd, 0, SEEK_END); 111 msgmap = mmap(0, msgmapsz, PROT_READ, MAP_SHARED, fd, 0); 112 close(fd); 113 if (msgmap == MAP_FAILED) 114 return -1; 115 /* check_magic */ 116 if (strcmp(msgmap, "MSGTXTS") != 0) { 117 msg_file(NULL); 118 return -1; 119 } 120 msgmapcount = atoi(msgmap + 8); 121 return 0; 122} 123 124const char * 125msg_string(msg msg_no) 126{ 127 int m = (intptr_t)msg_no; 128 129 if (m > sizeof msg_list / sizeof msg_list[0]) 130 /* guess that we were passed a text string */ 131 return msg_no; 132 133 if (msgmap != MAP_FAILED && m != 0 && m <= msgmapcount) { 134 unsigned int offset = atoi(msgmap + 8 + 8 * m); 135 if (offset != 0 && offset < msgmapsz) 136 return msgmap + offset; 137 } 138 139 return msg_list[m]; 140} 141 142void 143msg_clear(void) 144{ 145 146 wclear(msg_win); 147 last_o_was_punct = 0; 148 last_o_was_space = 1; 149} 150 151void 152msg_standout(void) 153{ 154 155 wstandout(msg_win); 156} 157 158void 159msg_standend(void) 160{ 161 162 wstandend(msg_win); 163} 164 165static int 166_msg_vprintf(int auto_fill, const char *fmt, va_list ap) 167{ 168 const char *wstart, *afterw; 169 int wordlen, nspaces; 170 int ret; 171 172 ret = vsnprintf(cbuffer, cbuffersize, fmt, ap); 173 174 if (!auto_fill) { 175 waddstr(msg_win, cbuffer); 176 177 /* 178 * nothing is perfect if they flow text after a table, 179 * but this may be decent. 180 */ 181 last_i_was_nl = last_i_was_space = 1; 182 last_o_was_punct = 0; 183 last_o_was_space = 1; 184 goto out; 185 } 186 187 for (wstart = afterw = cbuffer; *wstart; wstart = afterw) { 188 189 /* eat one space, or a whole word of non-spaces */ 190 if (isspace((unsigned char)*afterw)) 191 afterw++; 192 else 193 while (*afterw && !isspace((unsigned char)*afterw)) 194 afterw++; 195 196 /* this is an nl: special formatting necessary */ 197 if (*wstart == '\n') { 198 if (last_i_was_nl || last_i_was_space) { 199 200 if (getcurx(msg_win) != 0) 201 waddch(msg_win, '\n'); 202 if (last_i_was_nl) { 203 /* last was an nl: paragraph break */ 204 waddch(msg_win, '\n'); 205 } else { 206 /* last was space: line break */ 207 } 208 last_o_was_punct = 0; 209 last_o_was_space = 1; 210 } else { 211 /* last_o_was_punct unchanged */ 212 /* last_o_was_space unchanged */ 213 } 214 last_i_was_space = 1; 215 last_i_was_nl = 1; 216 continue; 217 } 218 219 /* this is a tab: special formatting necessary. */ 220 if (*wstart == '\t') { 221 if (last_i_was_nl) { 222 /* last was an nl: list indent */ 223 if (getcurx(msg_win) != 0) 224 waddch(msg_win, '\n'); 225 } else { 226 /* last was not an nl: columns */ 227 } 228 waddch(msg_win, '\t'); 229 last_i_was_nl = 0; 230 last_i_was_space = 1; 231 last_o_was_punct = 0; 232 last_o_was_space = 1; 233 continue; 234 } 235 236 /* this is a space: ignore it but set flags */ 237 last_i_was_nl = 0; /* all newlines handled above */ 238 last_i_was_space = isspace((unsigned char)*wstart); 239 if (last_i_was_space) 240 continue; 241 242 /* 243 * we have a real "word," i.e. a sequence of non-space 244 * characters. wstart is now the start of the word, 245 * afterw is the next character after the end. 246 */ 247 wordlen = afterw - wstart; 248 nspaces = last_o_was_space ? 0 : (last_o_was_punct ? 2 : 1); 249 if ((getcurx(msg_win) + nspaces + wordlen) >= 250 getmaxx(msg_win) && 251 wordlen < (getmaxx(msg_win) / 3)) { 252 /* wrap the line */ 253 waddch(msg_win, '\n'); 254 nspaces = 0; 255 } 256 257 /* output the word, preceded by spaces if necessary */ 258 while (nspaces-- > 0) 259 waddch(msg_win, ' '); 260 waddbytes(msg_win, wstart, wordlen); 261 262 /* set up the 'last' state for the next time around */ 263 switch (afterw[-1]) { 264 case '.': 265 case '?': 266 case '!': 267 last_o_was_punct = 1; 268 break; 269 default: 270 last_o_was_punct = 0; 271 break; 272 } 273 last_o_was_space = 0; 274 275 /* ... and do it all again! */ 276 } 277 278 /* String ended with a newline. They really want a line break. */ 279 if (last_i_was_nl) { 280 if (getcurx(msg_win) != 0) 281 waddch(msg_win, '\n'); 282 last_o_was_punct = 0; 283 last_o_was_space = 1; 284 } 285 286out: 287 wrefresh(msg_win); 288 return ret; 289} 290 291void 292msg_display(msg msg_no, ...) 293{ 294 va_list ap; 295 296 msg_clear(); 297 298 va_start(ap, msg_no); 299 (void)_msg_vprintf(1, msg_string(msg_no), ap); 300 va_end(ap); 301} 302 303void 304msg_display_add(msg msg_no, ...) 305{ 306 va_list ap; 307 308 va_start(ap, msg_no); 309 (void)_msg_vprintf(1, msg_string(msg_no), ap); 310 va_end(ap); 311} 312 313static void 314_msg_vprompt(const char *fmt, int flags, const char *def, char *val, 315 size_t max_chars, va_list ap) 316{ 317 int ch; 318 int len, pos, npos, off; 319 int first; 320 int txt_y, txt_x; 321 char *ibuf = alloca(max_chars); 322 int maxx; 323 324 keypad(msg_win, TRUE); 325 _msg_vprintf(0, fmt, ap); 326 ibuf[0] = 0; 327 if (def != NULL && *def) { 328 if (flags & MSG_PROMPT_HIDE_DFLT) 329 strlcpy(ibuf, def, max_chars); 330 else { 331 waddstr(msg_win, " ["); 332 waddstr(msg_win, def); 333 waddstr(msg_win, "]"); 334 } 335 } 336 waddstr(msg_win, ": "); 337 len = strlen(ibuf); 338 pos = len; 339 getyx(msg_win, txt_y, txt_x); 340 maxx = getmaxx(msg_win) - txt_x - 1; 341 off = 0; 342 343 for (first = 1; ; first = 0) { 344 345 if (flags & MSG_PROMPT_ECHO) { 346 /* shift text right as we near the buffer start */ 347 if (pos - off < 4) 348 off = pos - 4; 349 /* keep offset to a minimum if we are at the end */ 350 if (pos == len) 351 off = pos - maxx; 352 if (off < 0 || len <= maxx) 353 off = 0; 354 /* shift text left as we near the buffer end */ 355 npos = pos + 4; 356 if (npos > len) 357 npos = len; 358 if (npos - off > maxx) 359 off = npos - maxx; 360 /* calc. length to display */ 361 npos = len - off; 362 if (npos > maxx) 363 npos = maxx; 364 mvwaddnstr(msg_win, txt_y, txt_x, ibuf + off, npos); 365 wclrtoeol(msg_win); 366 if (off != 0) 367 mvwaddstr(msg_win, txt_y, txt_x, "+"); 368 wmove(msg_win, txt_y, txt_x + pos - off); 369 wrefresh(msg_win); 370 } 371 372 ch = wgetch(msg_win); 373 if (ch == '\n') 374 break; 375 376 switch (ch) { 377 case KEY_BACKSPACE: 378 case 'h' & 0x1f: case 0x7f: /* bs or del - delete left */ 379 if (first) { 380 /* delete all of default string */ 381 len = pos = 0; 382 break; 383 } 384 if (pos > 0) { 385 memmove(ibuf + pos - 1, ibuf + pos, len - pos); 386 len--; 387 pos--; 388 } else 389 _msg_beep(); 390 break; 391 case 'u' & 0x1f: /* ^U; line kill */ 392 /* kill line */ 393 len = pos = 0; 394 break; 395 case 'w' & 0x1f: /* ^W; word kill */ 396 /* 397 * word kill kills the spaces and the 'word' 398 * (non-spaces) last typed. the spaces before 399 * the 'word' aren't killed. 400 */ 401 npos = pos; 402 while (npos > 0 && isspace((unsigned char)ibuf[npos - 1])) 403 npos--; 404 while (npos > 0 && !isspace((unsigned char)ibuf[npos - 1])) 405 npos--; 406 memmove(ibuf + npos, ibuf + pos, len - pos); 407 len -= pos - npos; 408 pos = npos; 409 break; 410 case KEY_LEFT: 411 if (pos > 0) 412 pos--; 413 break; 414 case KEY_RIGHT: 415 if (len == 0 && pos == 0 && def != NULL) { 416 /* restore default! */ 417 strlcpy(ibuf, def, max_chars); 418 len = pos = strlen(ibuf); 419 break; 420 } 421 if (pos < len) 422 pos++; 423 break; 424 default: 425 if (len < (max_chars - 1) && isprint(ch)) { 426 memmove(ibuf + pos + 1, ibuf + pos, len - pos); 427 ibuf[pos++] = ch; 428 len++; 429 } else 430 _msg_beep(); 431 break; 432 } 433 } 434 435 if (flags & MSG_PROMPT_ECHO) { 436 mvwaddch(msg_win, txt_y, txt_x + len - off, '\n'); 437 last_o_was_punct = 0; 438 last_o_was_space = 1; 439 } 440 441 /* copy the appropriate string to the output */ 442 if (len != 0) { 443 ibuf[len] = '\0'; 444 strlcpy(val, ibuf, max_chars); 445 } else if (def != NULL && val != def) { 446 strlcpy(val, def, max_chars); 447 } 448} 449 450void 451msg_prompt(msg msg_no, const char *def, char *val, size_t max_chars, ...) 452{ 453 va_list ap; 454 455 msg_clear(); 456 457 va_start(ap, max_chars); 458 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, 459 def, val, max_chars, ap); 460 va_end(ap); 461} 462 463void 464msg_prompt_win(msg msg_no, int x, int y, int w, int h, 465 const char *def, char *val, size_t max_chars, ...) 466{ 467 va_list ap; 468 WINDOW *win, *svmsg, *sv_win; 469 int maxx, maxy; 470 int msg_flags = MSG_PROMPT_ECHO | MSG_PROMPT_HIDE_DFLT; 471 472 maxx = getmaxx(msg_win); 473 maxy = getmaxy(msg_win); 474 if (w == 0) { 475 va_start(ap, max_chars); 476 w = vsnprintf(NULL, 0, msg_string(msg_no), ap); 477 va_end(ap); 478 if (def != NULL && *def != 0 && w + max_chars * 2 < maxx) { 479 w += 2 + strlen(def) + 1; 480 msg_flags &= ~MSG_PROMPT_HIDE_DFLT; 481 } 482 w += 1 + 2 + max_chars + 1; 483 if (w > maxx) { 484 if (!(msg_flags & MSG_PROMPT_HIDE_DFLT)) { 485 w -= 2 + strlen(def) + 1; 486 msg_flags |= MSG_PROMPT_HIDE_DFLT; 487 } 488 w = maxx; 489 } 490 } 491 492 if (x == -1) 493 x = (maxx - w) / 2 + 1; 494 if (h < 3) 495 h = 3; 496 if (y < 3) 497 y = (maxy - h) / 2; 498 if (y + h > maxy) 499 y = maxy - h; 500 501 win = subwin(msg_win, h, w, y, x); 502 if (win == NULL) 503 wprintw(msg_win, "msg_prompt_win: " 504 "newwin(%d, %d, %d, %d) failed\n", 505 h, w, y, x); 506 else { 507 /* 508 * Save screen contents from under our window 509 * Due to a mis-feature of NetBSD curses, curscr contains 510 * the data processed by doupdate() not that by wnoutrefresh(). 511 * We must call doupdate() here to ensure we save the correct 512 * data. See PR 26660 513 */ 514 doupdate(); 515 sv_win = dupwin(win); 516 if (sv_win) 517 overwrite(curscr, sv_win); 518 wbkgd(win, getbkgd(msg_win)); 519 wattrset(win, getattrs(msg_win)); 520 box(win, 0, 0); 521 wrefresh(win); 522 523 /* Change message window to be our little box */ 524 svmsg = msg_window(subwin(msg_win, h - 2, w - 2, y + 1, x + 1)); 525 wbkgd(msg_win, getbkgd(win)); 526 wattrset(msg_win, getattrs(win)); 527 528 msg_clear(); 529 } 530 531 va_start(ap, max_chars); 532 _msg_vprompt(msg_string(msg_no), msg_flags, def, val, max_chars, ap); 533 va_end(ap); 534 535 if (win != NULL) { 536 wclear(win); 537 if (sv_win) { 538 /* Restore original screen contents */ 539 overwrite(sv_win, win); 540 delwin(sv_win); 541 } 542 wnoutrefresh(win); 543 /* Restore normal message window */ 544 delwin(msg_window(svmsg)); 545 delwin(win); 546 } 547} 548 549void 550msg_prompt_add(msg msg_no, const char *def, char *val, size_t max_chars, ...) 551{ 552 va_list ap; 553 554 va_start(ap, max_chars); 555 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, def, val, max_chars, ap); 556 va_end(ap); 557} 558 559void 560msg_prompt_noecho(msg msg_no, const char *def, char *val, size_t max_chars, ...) 561{ 562 va_list ap; 563 564 msg_clear(); 565 566 va_start(ap, max_chars); 567 _msg_vprompt(msg_string(msg_no), 0, def, val, max_chars, ap); 568 va_end(ap); 569} 570 571void 572msg_table_add(msg msg_no, ...) 573{ 574 va_list ap; 575 576 va_start(ap, msg_no); 577 (void)_msg_vprintf(0, msg_string(msg_no), ap); 578 va_end(ap); 579} 580 581int 582msg_row(void) 583{ 584 585 return getcury(msg_win) + getbegy(msg_win); 586} 587