1/* $NetBSD: msg_sys.def,v 1.27 2004/06/05 19:05:36 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(*afterw)) 191 afterw++; 192 else 193 while (*afterw && !isspace(*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(*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_erase_ch(void) 315{ 316 int y, x; 317 318 getyx(msg_win, y, x); 319 x--; 320 wmove(msg_win, y, x); 321 waddch(msg_win, ' '); 322 wmove(msg_win, y, x); 323} 324 325static void 326_msg_vprompt(const char *fmt, int flags, const char *def, char *val, 327 size_t max_chars, va_list ap) 328{ 329 int ch; 330 int len, pos, npos, off; 331 int first; 332 int txt_y, txt_x; 333 char *ibuf = alloca(max_chars); 334 int maxx; 335 336 keypad(msg_win, TRUE); 337 _msg_vprintf(0, fmt, ap); 338 ibuf[0] = 0; 339 if (def != NULL && *def) { 340 if (flags & MSG_PROMPT_HIDE_DFLT) 341 strlcpy(ibuf, def, max_chars); 342 else { 343 waddstr(msg_win, " ["); 344 waddstr(msg_win, def); 345 waddstr(msg_win, "]"); 346 } 347 } 348 waddstr(msg_win, ": "); 349 len = strlen(ibuf); 350 pos = len; 351 getyx(msg_win, txt_y, txt_x); 352 maxx = getmaxx(msg_win) - txt_x - 1; 353 off = 0; 354 355 for (first = 1; ; first = 0) { 356 357 if (flags & MSG_PROMPT_ECHO) { 358 /* shift text right as we near the buffer start */ 359 if (pos - off < 4) 360 off = pos - 4; 361 /* keep offset to a minimum if we are at the end */ 362 if (pos == len) 363 off = pos - maxx; 364 if (off < 0 || len <= maxx) 365 off = 0; 366 /* shift text left as we near the buffer end */ 367 npos = pos + 4; 368 if (npos > len) 369 npos = len; 370 if (npos - off > maxx) 371 off = npos - maxx; 372 /* calc. length to display */ 373 npos = len - off; 374 if (npos > maxx) 375 npos = maxx; 376 mvwaddnstr(msg_win, txt_y, txt_x, ibuf + off, npos); 377 wclrtoeol(msg_win); 378 if (off != 0) 379 mvwaddstr(msg_win, txt_y, txt_x, "+"); 380 wmove(msg_win, txt_y, txt_x + pos - off); 381 wrefresh(msg_win); 382 } 383 384 ch = wgetch(msg_win); 385 if (ch == '\n') 386 break; 387 388 switch (ch) { 389 case KEY_BACKSPACE: 390 case 'h' & 0x1f: case 0x7f: /* bs or del - delete left */ 391 if (first) { 392 /* delete all of default string */ 393 len = pos = 0; 394 break; 395 } 396 if (pos > 0) { 397 memmove(ibuf + pos - 1, ibuf + pos, len - pos); 398 len--; 399 pos--; 400 } else 401 _msg_beep(); 402 break; 403 case 'u' & 0x1f: /* ^U; line kill */ 404 /* kill line */ 405 len = pos = 0; 406 break; 407 case 'w' & 0x1f: /* ^W; word kill */ 408 /* 409 * word kill kills the spaces and the 'word' 410 * (non-spaces) last typed. the spaces before 411 * the 'word' aren't killed. 412 */ 413 npos = pos; 414 while (npos > 0 && isspace(ibuf[npos - 1])) 415 npos--; 416 while (npos > 0 && !isspace(ibuf[npos - 1])) 417 npos--; 418 memmove(ibuf + npos, ibuf + pos, len - pos); 419 len -= pos - npos; 420 pos = npos; 421 break; 422 case KEY_LEFT: 423 if (pos > 0) 424 pos--; 425 break; 426 case KEY_RIGHT: 427 if (len == 0 && pos == 0 && def != NULL) { 428 /* restore default! */ 429 strlcpy(ibuf, def, max_chars); 430 len = pos = strlen(ibuf); 431 break; 432 } 433 if (pos < len) 434 pos++; 435 break; 436 default: 437 if (len < (max_chars - 1) && isprint(ch)) { 438 memmove(ibuf + pos + 1, ibuf + pos, len - pos); 439 ibuf[pos++] = ch; 440 len++; 441 } else 442 _msg_beep(); 443 break; 444 } 445 } 446 447 if (flags & MSG_PROMPT_ECHO) { 448 mvwaddch(msg_win, txt_y, txt_x + len - off, '\n'); 449 last_o_was_punct = 0; 450 last_o_was_space = 1; 451 } 452 453 /* copy the appropriate string to the output */ 454 if (len != 0) { 455 ibuf[len] = '\0'; 456 strlcpy(val, ibuf, max_chars); 457 } else if (def != NULL && val != def) { 458 strlcpy(val, def, max_chars); 459 } 460} 461 462void 463msg_prompt(msg msg_no, const char *def, char *val, size_t max_chars, ...) 464{ 465 va_list ap; 466 467 msg_clear(); 468 469 va_start(ap, max_chars); 470 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, 471 def, val, max_chars, ap); 472 va_end(ap); 473} 474 475void 476msg_prompt_win(msg msg_no, int x, int y, int w, int h, 477 const char *def, char *val, size_t max_chars, ...) 478{ 479 va_list ap; 480 WINDOW *win, *svwin; 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, max_chars); 488 w = vsnprintf(NULL, 0, msg_string(msg_no), ap); 489 va_end(ap); 490 if (def != NULL && *def != 0 && w + max_chars * 2 < maxx) { 491 w += 2 + strlen(def) + 1; 492 msg_flags &= ~MSG_PROMPT_HIDE_DFLT; 493 } 494 w += 1 + 2 + max_chars + 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 wbkgd(win, getbkgd(msg_win)); 520 wattrset(win, getattrs(msg_win)); 521 box(win, 0, 0); 522 wrefresh(win); 523 524 /* Change message window to be our little box */ 525 svwin = msg_window(subwin(msg_win, h - 2, w - 2, y + 1, x + 1)); 526 wbkgd(msg_win, getbkgd(win)); 527 wattrset(msg_win, getattrs(win)); 528 529 msg_clear(); 530 } 531 532 va_start(ap, max_chars); 533 _msg_vprompt(msg_string(msg_no), msg_flags, def, val, max_chars, ap); 534 va_end(ap); 535 536 if (win != NULL) { 537 wclear(win); 538 wrefresh(win); 539 /* Restore normal message window */ 540 delwin(msg_window(svwin)); 541 delwin(win); 542 } 543} 544 545void 546msg_prompt_add(msg msg_no, const char *def, char *val, size_t max_chars, ...) 547{ 548 va_list ap; 549 550 va_start(ap, max_chars); 551 _msg_vprompt(msg_string(msg_no), MSG_PROMPT_ECHO, def, val, max_chars, ap); 552 va_end(ap); 553} 554 555void 556msg_prompt_noecho(msg msg_no, const char *def, char *val, size_t max_chars, ...) 557{ 558 va_list ap; 559 560 msg_clear(); 561 562 va_start(ap, max_chars); 563 _msg_vprompt(msg_string(msg_no), 0, def, val, max_chars, ap); 564 va_end(ap); 565} 566 567void 568msg_table_add(msg msg_no, ...) 569{ 570 va_list ap; 571 572 va_start(ap, msg_no); 573 (void)_msg_vprintf(0, msg_string(msg_no), ap); 574 va_end(ap); 575} 576 577int 578msg_row(void) 579{ 580 581 return getcury(msg_win) + getbegy(msg_win); 582} 583