1/* $NetBSD: msg_sys.def,v 1.38 2010/01/02 16:08:20 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 unsigned 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 uintptr_t m = (uintptr_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 val_buf_len, 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; 322 int maxx; 323 324 if (val == NULL || val_buf_len == 0) { 325 /* No answer wanted */ 326 val = NULL; 327 val_buf_len = 1; 328 } 329 330 ibuf = malloc(val_buf_len); 331 332 keypad(msg_win, TRUE); 333 _msg_vprintf(0, fmt, ap); 334 ibuf[0] = 0; 335 if (def != NULL && *def) { 336 if (flags & MSG_PROMPT_HIDE_DFLT) 337 strlcpy(ibuf, def, val_buf_len); 338 else { 339 waddstr(msg_win, " ["); 340 waddstr(msg_win, def); 341 waddstr(msg_win, "]"); 342 } 343 } 344 waddstr(msg_win, ": "); 345 len = strlen(ibuf); 346 pos = len; 347 getyx(msg_win, txt_y, txt_x); 348 maxx = getmaxx(msg_win) - txt_x - 1; 349 off = 0; 350 351 for (first = 1; ; first = 0) { 352 353 if (flags & MSG_PROMPT_ECHO) { 354 /* shift text right as we near the buffer start */ 355 if (pos - off < 4) 356 off = pos - 4; 357 /* keep offset to a minimum if we are at the end */ 358 if (pos == len) 359 off = pos - maxx; 360 if (off < 0 || len <= maxx) 361 off = 0; 362 /* shift text left as we near the buffer end */ 363 npos = pos + 4; 364 if (npos > len) 365 npos = len; 366 if (npos - off > maxx) 367 off = npos - maxx; 368 /* calc. length to display */ 369 npos = len - off; 370 if (npos > maxx) 371 npos = maxx; 372 mvwaddnstr(msg_win, txt_y, txt_x, ibuf + off, npos); 373 wclrtoeol(msg_win); 374 if (off != 0) 375 mvwaddstr(msg_win, txt_y, txt_x, "+"); 376 wmove(msg_win, txt_y, txt_x + pos - off); 377 wrefresh(msg_win); 378 } 379 380 ch = wgetch(msg_win); 381 if (ch == '\n') 382 break; 383 384 switch (ch) { 385 case KEY_BACKSPACE: 386 case 'h' & 0x1f: case 0x7f: /* bs or del - delete left */ 387 if (first) { 388 /* delete all of default string */ 389 len = pos = 0; 390 break; 391 } 392 if (pos > 0) { 393 memmove(ibuf + pos - 1, ibuf + pos, len - pos); 394 len--; 395 pos--; 396 } else 397 _msg_beep(); 398 break; 399 case 'u' & 0x1f: /* ^U; line kill */ 400 /* kill line */ 401 len = pos = 0; 402 break; 403 case 'w' & 0x1f: /* ^W; word kill */ 404 /* 405 * word kill kills the spaces and the 'word' 406 * (non-spaces) last typed. the spaces before 407 * the 'word' aren't killed. 408 */ 409 npos = pos; 410 while (npos > 0 && isspace((unsigned char)ibuf[npos - 1])) 411 npos--; 412 while (npos > 0 && !isspace((unsigned char)ibuf[npos - 1])) 413 npos--; 414 memmove(ibuf + npos, ibuf + pos, len - pos); 415 len -= pos - npos; 416 pos = npos; 417 break; 418 case KEY_LEFT: 419 if (pos > 0) 420 pos--; 421 break; 422 case KEY_RIGHT: 423 if (len == 0 && pos == 0 && def != NULL) { 424 /* restore default! */ 425 strlcpy(ibuf, def, val_buf_len); 426 len = pos = strlen(ibuf); 427 break; 428 } 429 if (pos < len) 430 pos++; 431 break; 432 default: 433 if (len < (int)(val_buf_len - 1) && isprint(ch)) { 434 memmove(ibuf + pos + 1, ibuf + pos, len - pos); 435 ibuf[pos++] = ch; 436 len++; 437 } else 438 _msg_beep(); 439 break; 440 } 441 } 442 443 if (flags & MSG_PROMPT_ECHO) { 444 mvwaddch(msg_win, txt_y, txt_x + len - off, '\n'); 445 last_o_was_punct = 0; 446 last_o_was_space = 1; 447 } 448 449 if (val != NULL) { 450 /* copy the appropriate string to the output */ 451 if (len != 0 || flags & MSG_PROMPT_HIDE_DFLT) { 452 ibuf[len] = '\0'; 453 strlcpy(val, ibuf, val_buf_len); 454 } else if (def != NULL && val != def) { 455 strlcpy(val, def, val_buf_len); 456 } 457 } 458 free(ibuf); 459} 460 461void 462msg_prompt(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 463{ 464 va_list ap; 465 466 msg_clear(); 467 468 va_start(ap, val_buf_len); 469 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, 470 def, val, val_buf_len, ap); 471 va_end(ap); 472} 473 474void 475msg_prompt_win(msg msg_no, int x, int y, int w, int h, 476 const char *def, char *val, size_t val_buf_len, ...) 477{ 478 va_list ap; 479 WINDOW *win; 480 WINDOW *svmsg = NULL, *sv_win = NULL; /* XXX -Wuninitialized [many] */ 481 int maxx, maxy; 482 int msg_flags = MSG_PROMPT_ECHO | MSG_PROMPT_HIDE_DFLT; 483 484 maxx = getmaxx(msg_win); 485 maxy = getmaxy(msg_win); 486 if (w == 0) { 487 va_start(ap, val_buf_len); 488 w = vsnprintf(NULL, 0, msg_string(msg_no), ap); 489 va_end(ap); 490 if (def != NULL && *def != 0 && w + (int)val_buf_len * 2 < maxx) { 491 w += 2 + strlen(def) + 1; 492 msg_flags &= ~MSG_PROMPT_HIDE_DFLT; 493 } 494 w += 1 + 2 + val_buf_len + 1; 495 if (w > maxx) { 496 if (!(msg_flags & MSG_PROMPT_HIDE_DFLT)) { 497 w -= 2 + strlen(def) + 1; 498 msg_flags |= MSG_PROMPT_HIDE_DFLT; 499 } 500 w = maxx; 501 } 502 } 503 504 if (x == -1) 505 x = (maxx - w) / 2 + 1; 506 if (h < 3) 507 h = 3; 508 if (y < 3) 509 y = (maxy - h) / 2; 510 if (y + h > maxy) 511 y = maxy - h; 512 513 win = subwin(msg_win, h, w, y, x); 514 if (win == NULL) 515 wprintw(msg_win, "msg_prompt_win: " 516 "newwin(%d, %d, %d, %d) failed\n", 517 h, w, y, x); 518 else { 519 /* 520 * Save screen contents from under our window 521 * Due to a mis-feature of NetBSD curses, curscr contains 522 * the data processed by doupdate() not that by wnoutrefresh(). 523 * We must call doupdate() here to ensure we save the correct 524 * data. See PR 26660 525 */ 526 doupdate(); 527 sv_win = dupwin(win); 528 if (sv_win) 529 overwrite(curscr, sv_win); 530 wbkgd(win, getbkgd(msg_win)); 531 wattrset(win, getattrs(msg_win)); 532 box(win, 0, 0); 533 wrefresh(win); 534 535 /* Change message window to be our little box */ 536 svmsg = msg_window(subwin(msg_win, h - 2, w - 2, y + 1, x + 1)); 537 wbkgd(msg_win, getbkgd(win)); 538 wattrset(msg_win, getattrs(win)); 539 540 msg_clear(); 541 } 542 543 va_start(ap, val_buf_len); 544 _msg_vprompt(msg_string(msg_no), msg_flags, def, val, val_buf_len, ap); 545 va_end(ap); 546 547 if (win != NULL) { 548 wclear(win); 549 if (sv_win) { 550 /* Restore original screen contents */ 551 overwrite(sv_win, win); 552 delwin(sv_win); 553 } 554 wnoutrefresh(win); 555 /* Restore normal message window */ 556 delwin(msg_window(svmsg)); 557 delwin(win); 558 } 559} 560 561void 562msg_prompt_add(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 563{ 564 va_list ap; 565 566 va_start(ap, val_buf_len); 567 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, def, val, val_buf_len, ap); 568 va_end(ap); 569} 570 571void 572msg_prompt_noecho(msg msg_no, const char *def, char *val, size_t val_buf_len, ...) 573{ 574 va_list ap; 575 576 msg_clear(); 577 578 va_start(ap, val_buf_len); 579 _msg_vprompt(msg_string(msg_no), 0, def, val, val_buf_len, ap); 580 va_end(ap); 581} 582 583void 584msg_table_add(msg msg_no, ...) 585{ 586 va_list ap; 587 588 va_start(ap, msg_no); 589 (void)_msg_vprintf(0, msg_string(msg_no), ap); 590 va_end(ap); 591} 592 593int 594msg_row(void) 595{ 596 597 return getcury(msg_win) + getbegy(msg_win); 598} 599