1 /* 2 * $Id: dlg_keys.c,v 1.26 2009/02/22 16:19:51 tom Exp $ 3 * 4 * dlg_keys.c -- runtime binding support for dialog 5 * 6 * Copyright 2006-2007,2009 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 */ 23 24 #include <dialog.h> 25 #include <dlg_keys.h> 26 27 #define LIST_BINDINGS struct _list_bindings 28 29 LIST_BINDINGS { 30 LIST_BINDINGS *link; 31 WINDOW *win; /* window on which widget gets input */ 32 const char *name; /* widget name */ 33 bool buttons; /* true only for dlg_register_buttons() */ 34 DLG_KEYS_BINDING *binding; /* list of bindings */ 35 }; 36 37 static LIST_BINDINGS *all_bindings; 38 static const DLG_KEYS_BINDING end_keys_binding = END_KEYS_BINDING; 39 40 /* 41 * For a given named widget's window, associate a binding table. 42 */ 43 void 44 dlg_register_window(WINDOW *win, const char *name, DLG_KEYS_BINDING * binding) 45 { 46 LIST_BINDINGS *p, *q; 47 48 for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) { 49 if (p->win == win && !strcmp(p->name, name)) { 50 p->binding = binding; 51 return; 52 } 53 } 54 /* add built-in bindings at the end of the list (see compare_bindings). */ 55 if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) { 56 p->win = win; 57 p->name = name; 58 p->binding = binding; 59 if (q != 0) 60 q->link = p; 61 else 62 all_bindings = p; 63 } 64 } 65 66 /* 67 * Unlike dlg_lookup_key(), this looks for either widget-builtin or rc-file 68 * definitions, depending on whether 'win' is null. 69 */ 70 static int 71 key_is_bound(WINDOW *win, const char *name, int curses_key, int function_key) 72 { 73 LIST_BINDINGS *p; 74 75 for (p = all_bindings; p != 0; p = p->link) { 76 if (p->win == win && !dlg_strcmp(p->name, name)) { 77 int n; 78 for (n = 0; p->binding[n].is_function_key >= 0; ++n) { 79 if (p->binding[n].curses_key == curses_key 80 && p->binding[n].is_function_key == function_key) { 81 return TRUE; 82 } 83 } 84 } 85 } 86 return FALSE; 87 } 88 89 /* 90 * Call this function after dlg_register_window(), for the list of button 91 * labels associated with the widget. 92 * 93 * Ensure that dlg_lookup_key() will not accidentally translate a key that 94 * we would like to use for a button abbreviation to some other key, e.g., 95 * h/j/k/l for navigation into a cursor key. Do this by binding the key 96 * to itself. 97 * 98 * See dlg_char_to_button(). 99 */ 100 void 101 dlg_register_buttons(WINDOW *win, const char *name, const char **buttons) 102 { 103 int n; 104 LIST_BINDINGS *p; 105 DLG_KEYS_BINDING *q; 106 107 if (buttons == 0) 108 return; 109 110 for (n = 0; buttons[n] != 0; ++n) { 111 int curses_key = dlg_button_to_char(buttons[n]); 112 113 /* ignore multibyte characters */ 114 if (curses_key >= KEY_MIN) 115 continue; 116 117 /* if it is not bound in the widget, skip it (no conflicts) */ 118 if (!key_is_bound(win, name, curses_key, FALSE)) 119 continue; 120 121 #ifdef HAVE_RC_FILE 122 /* if it is bound in the rc-file, skip it */ 123 if (key_is_bound(0, name, curses_key, FALSE)) 124 continue; 125 #endif 126 127 if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) { 128 if ((q = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0) { 129 q[0].is_function_key = 0; 130 q[0].curses_key = curses_key; 131 q[0].dialog_key = curses_key; 132 q[1] = end_keys_binding; 133 134 p->win = win; 135 p->name = name; 136 p->buttons = TRUE; 137 p->binding = q; 138 139 /* put these at the beginning, to override the widget's table */ 140 p->link = all_bindings; 141 all_bindings = p; 142 } else { 143 free(p); 144 } 145 } 146 } 147 } 148 149 /* 150 * Remove the bindings for a given window. 151 */ 152 void 153 dlg_unregister_window(WINDOW *win) 154 { 155 LIST_BINDINGS *p, *q; 156 157 for (p = all_bindings, q = 0; p != 0; p = p->link) { 158 if (p->win == win) { 159 if (q != 0) { 160 q->link = p->link; 161 } else { 162 all_bindings = p->link; 163 } 164 /* the user-defined and buttons-bindings all are length=1 */ 165 if (p->binding[1].is_function_key < 0) 166 free(p->binding); 167 free(p); 168 dlg_unregister_window(win); 169 break; 170 } 171 q = p; 172 } 173 } 174 175 /* 176 * Call this after wgetch(), using the same window pointer and passing 177 * the curses-key. 178 * 179 * If there is no binding associated with the widget, it simply returns 180 * the given curses-key. 181 * 182 * Parameters: 183 * win is the window on which the wgetch() was done. 184 * curses_key is the value returned by wgetch(). 185 * fkey in/out (on input, it is true if curses_key is a function key, 186 * and on output, it is true if the result is a function key). 187 */ 188 int 189 dlg_lookup_key(WINDOW *win, int curses_key, int *fkey) 190 { 191 LIST_BINDINGS *p; 192 int n; 193 194 /* 195 * Ignore mouse clicks, since they are already encoded properly. 196 */ 197 #ifdef KEY_MOUSE 198 if (*fkey != 0 && curses_key == KEY_MOUSE) { 199 ; 200 } else 201 #endif 202 /* 203 * Ignore resize events, since they are already encoded properly. 204 */ 205 #ifdef KEY_RESIZE 206 if (*fkey != 0 && curses_key == KEY_RESIZE) { 207 ; 208 } else 209 #endif 210 if (*fkey == 0 || curses_key < KEY_MAX) { 211 for (p = all_bindings; p != 0; p = p->link) { 212 if (p->win == win || p->win == 0) { 213 int function_key = (*fkey != 0); 214 for (n = 0; p->binding[n].is_function_key >= 0; ++n) { 215 if (p->buttons 216 && !function_key 217 && p->binding[n].curses_key == (int) dlg_toupper(curses_key)) { 218 *fkey = 0; 219 return p->binding[n].dialog_key; 220 } 221 if (p->binding[n].curses_key == curses_key 222 && p->binding[n].is_function_key == function_key) { 223 *fkey = p->binding[n].dialog_key; 224 return *fkey; 225 } 226 } 227 } 228 } 229 } 230 return curses_key; 231 } 232 233 /* 234 * Test a dialog internal keycode to see if it corresponds to one of the push 235 * buttons on the widget such as "OK". 236 * 237 * This is only useful if there are user-defined key bindings, since there are 238 * no built-in bindings that map directly to DLGK_OK, etc. 239 * 240 * See also dlg_ok_buttoncode(). 241 */ 242 int 243 dlg_result_key(int dialog_key, int fkey GCC_UNUSED, int *resultp) 244 { 245 int done = FALSE; 246 247 #ifdef HAVE_RC_FILE 248 if (fkey) { 249 switch ((DLG_KEYS_ENUM) dialog_key) { 250 case DLGK_OK: 251 *resultp = DLG_EXIT_OK; 252 done = TRUE; 253 break; 254 case DLGK_CANCEL: 255 if (!dialog_vars.nocancel) { 256 *resultp = DLG_EXIT_CANCEL; 257 done = TRUE; 258 } 259 break; 260 case DLGK_EXTRA: 261 if (dialog_vars.extra_button) { 262 *resultp = DLG_EXIT_EXTRA; 263 done = TRUE; 264 } 265 break; 266 case DLGK_HELP: 267 if (dialog_vars.help_button) { 268 *resultp = DLG_EXIT_HELP; 269 done = TRUE; 270 } 271 break; 272 case DLGK_ESC: 273 *resultp = DLG_EXIT_ESC; 274 done = TRUE; 275 break; 276 default: 277 break; 278 } 279 } else 280 #endif 281 if (dialog_key == ESC) { 282 *resultp = DLG_EXIT_ESC; 283 done = TRUE; 284 } else if (dialog_key == ERR) { 285 *resultp = DLG_EXIT_ERROR; 286 done = TRUE; 287 } 288 289 return done; 290 } 291 292 #ifdef HAVE_RC_FILE 293 typedef struct { 294 const char *name; 295 int code; 296 } CODENAME; 297 298 #define CURSES_NAME(upper) { #upper, KEY_ ## upper } 299 #define COUNT_CURSES sizeof(curses_names)/sizeof(curses_names[0]) 300 static const CODENAME curses_names[] = 301 { 302 CURSES_NAME(DOWN), 303 CURSES_NAME(UP), 304 CURSES_NAME(LEFT), 305 CURSES_NAME(RIGHT), 306 CURSES_NAME(HOME), 307 CURSES_NAME(BACKSPACE), 308 CURSES_NAME(F0), 309 CURSES_NAME(DL), 310 CURSES_NAME(IL), 311 CURSES_NAME(DC), 312 CURSES_NAME(IC), 313 CURSES_NAME(EIC), 314 CURSES_NAME(CLEAR), 315 CURSES_NAME(EOS), 316 CURSES_NAME(EOL), 317 CURSES_NAME(SF), 318 CURSES_NAME(SR), 319 CURSES_NAME(NPAGE), 320 CURSES_NAME(PPAGE), 321 CURSES_NAME(STAB), 322 CURSES_NAME(CTAB), 323 CURSES_NAME(CATAB), 324 CURSES_NAME(ENTER), 325 CURSES_NAME(PRINT), 326 CURSES_NAME(LL), 327 CURSES_NAME(A1), 328 CURSES_NAME(A3), 329 CURSES_NAME(B2), 330 CURSES_NAME(C1), 331 CURSES_NAME(C3), 332 CURSES_NAME(BTAB), 333 CURSES_NAME(BEG), 334 CURSES_NAME(CANCEL), 335 CURSES_NAME(CLOSE), 336 CURSES_NAME(COMMAND), 337 CURSES_NAME(COPY), 338 CURSES_NAME(CREATE), 339 CURSES_NAME(END), 340 CURSES_NAME(EXIT), 341 CURSES_NAME(FIND), 342 CURSES_NAME(HELP), 343 CURSES_NAME(MARK), 344 CURSES_NAME(MESSAGE), 345 CURSES_NAME(MOVE), 346 CURSES_NAME(NEXT), 347 CURSES_NAME(OPEN), 348 CURSES_NAME(OPTIONS), 349 CURSES_NAME(PREVIOUS), 350 CURSES_NAME(REDO), 351 CURSES_NAME(REFERENCE), 352 CURSES_NAME(REFRESH), 353 CURSES_NAME(REPLACE), 354 CURSES_NAME(RESTART), 355 CURSES_NAME(RESUME), 356 CURSES_NAME(SAVE), 357 CURSES_NAME(SBEG), 358 CURSES_NAME(SCANCEL), 359 CURSES_NAME(SCOMMAND), 360 CURSES_NAME(SCOPY), 361 CURSES_NAME(SCREATE), 362 CURSES_NAME(SDC), 363 CURSES_NAME(SDL), 364 CURSES_NAME(SELECT), 365 CURSES_NAME(SEND), 366 CURSES_NAME(SEOL), 367 CURSES_NAME(SEXIT), 368 CURSES_NAME(SFIND), 369 CURSES_NAME(SHELP), 370 CURSES_NAME(SHOME), 371 CURSES_NAME(SIC), 372 CURSES_NAME(SLEFT), 373 CURSES_NAME(SMESSAGE), 374 CURSES_NAME(SMOVE), 375 CURSES_NAME(SNEXT), 376 CURSES_NAME(SOPTIONS), 377 CURSES_NAME(SPREVIOUS), 378 CURSES_NAME(SPRINT), 379 CURSES_NAME(SREDO), 380 CURSES_NAME(SREPLACE), 381 CURSES_NAME(SRIGHT), 382 CURSES_NAME(SRSUME), 383 CURSES_NAME(SSAVE), 384 CURSES_NAME(SSUSPEND), 385 CURSES_NAME(SUNDO), 386 CURSES_NAME(SUSPEND), 387 CURSES_NAME(UNDO), 388 }; 389 390 #define DIALOG_NAME(upper) { #upper, DLGK_ ## upper } 391 #define COUNT_DIALOG sizeof(dialog_names)/sizeof(dialog_names[0]) 392 static const CODENAME dialog_names[] = 393 { 394 DIALOG_NAME(OK), 395 DIALOG_NAME(CANCEL), 396 DIALOG_NAME(EXTRA), 397 DIALOG_NAME(HELP), 398 DIALOG_NAME(ESC), 399 DIALOG_NAME(PAGE_FIRST), 400 DIALOG_NAME(PAGE_LAST), 401 DIALOG_NAME(PAGE_NEXT), 402 DIALOG_NAME(PAGE_PREV), 403 DIALOG_NAME(ITEM_FIRST), 404 DIALOG_NAME(ITEM_LAST), 405 DIALOG_NAME(ITEM_NEXT), 406 DIALOG_NAME(ITEM_PREV), 407 DIALOG_NAME(FIELD_FIRST), 408 DIALOG_NAME(FIELD_LAST), 409 DIALOG_NAME(FIELD_NEXT), 410 DIALOG_NAME(FIELD_PREV), 411 DIALOG_NAME(GRID_UP), 412 DIALOG_NAME(GRID_DOWN), 413 DIALOG_NAME(GRID_LEFT), 414 DIALOG_NAME(GRID_RIGHT), 415 DIALOG_NAME(DELETE_LEFT), 416 DIALOG_NAME(DELETE_RIGHT), 417 DIALOG_NAME(DELETE_ALL), 418 DIALOG_NAME(ENTER), 419 DIALOG_NAME(BEGIN), 420 DIALOG_NAME(FINAL), 421 DIALOG_NAME(SELECT) 422 }; 423 424 static char * 425 skip_white(char *s) 426 { 427 while (*s != '\0' && isspace(UCH(*s))) 428 ++s; 429 return s; 430 } 431 432 static char * 433 skip_black(char *s) 434 { 435 while (*s != '\0' && !isspace(UCH(*s))) 436 ++s; 437 return s; 438 } 439 440 /* 441 * Find a user-defined binding, given the curses key code. 442 */ 443 static DLG_KEYS_BINDING * 444 find_binding(char *widget, int curses_key) 445 { 446 LIST_BINDINGS *p; 447 DLG_KEYS_BINDING *result = 0; 448 449 for (p = all_bindings; p != 0; p = p->link) { 450 if (p->win == 0 451 && !dlg_strcmp(p->name, widget) 452 && p->binding->curses_key == curses_key) { 453 result = p->binding; 454 break; 455 } 456 } 457 return result; 458 } 459 460 /* 461 * Built-in bindings have a nonzero "win" member, and the associated binding 462 * table can have more than one entry. We keep those last, since lookups will 463 * find the user-defined bindings first and use those. 464 * 465 * Sort "*" (all-widgets) entries past named widgets, since those are less 466 * specific. 467 */ 468 static int 469 compare_bindings(LIST_BINDINGS * a, LIST_BINDINGS * b) 470 { 471 int result = 0; 472 if (a->win == b->win) { 473 if (!strcmp(a->name, b->name)) { 474 result = a->binding[0].curses_key - b->binding[0].curses_key; 475 } else if (!strcmp(b->name, "*")) { 476 result = -1; 477 } else if (!strcmp(a->name, "*")) { 478 result = 1; 479 } else { 480 result = dlg_strcmp(a->name, b->name); 481 } 482 } else if (b->win) { 483 result = -1; 484 } else { 485 result = 1; 486 } 487 return result; 488 } 489 490 /* 491 * Find a user-defined binding, given the curses key code. If it does not 492 * exist, create a new one, inserting it into the linked list, keeping it 493 * sorted to simplify lookups for user-defined bindings that can override 494 * the built-in bindings. 495 */ 496 static DLG_KEYS_BINDING * 497 make_binding(char *widget, int curses_key, int is_function, int dialog_key) 498 { 499 LIST_BINDINGS *entry = 0; 500 DLG_KEYS_BINDING *data = 0; 501 char *name; 502 LIST_BINDINGS *p, *q; 503 DLG_KEYS_BINDING *result = find_binding(widget, curses_key); 504 505 if (result == 0 506 && (entry = dlg_calloc(LIST_BINDINGS, 1)) != 0 507 && (data = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0 508 && (name = dlg_strclone(widget)) != 0) { 509 510 entry->name = name; 511 entry->binding = data; 512 513 data[0].is_function_key = is_function; 514 data[0].curses_key = curses_key; 515 data[0].dialog_key = dialog_key; 516 517 data[1] = end_keys_binding; 518 519 for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) { 520 if (compare_bindings(entry, p) < 0) { 521 break; 522 } 523 } 524 if (q != 0) { 525 q->link = entry; 526 } else { 527 all_bindings = entry; 528 } 529 if (p != 0) { 530 entry->link = p; 531 } 532 result = data; 533 } else if (entry != 0) { 534 free(entry); 535 if (data) 536 free(data); 537 } 538 539 return result; 540 } 541 542 /* 543 * Parse the parameters of the "bindkeys" configuration-file entry. This 544 * expects widget name which may be "*", followed by curses key definition and 545 * then dialog key definition. 546 * 547 * The curses key "should" be one of the names (ignoring case) from 548 * curses_names[], but may also be a single control character (prefix "^" or 549 * "~" depending on whether it is C0 or C1), or an escaped single character. 550 * Binding a printable character with dialog is possible but not useful. 551 * 552 * The dialog key must be one of the names from dialog_names[]. 553 */ 554 int 555 dlg_parse_bindkey(char *params) 556 { 557 char *p = skip_white(params); 558 char *q; 559 bool escaped = FALSE; 560 int modified = 0; 561 int result = FALSE; 562 unsigned xx; 563 char *widget; 564 int is_function = FALSE; 565 int curses_key; 566 int dialog_key; 567 568 curses_key = -1; 569 dialog_key = -1; 570 widget = p; 571 572 p = skip_black(p); 573 if (p != widget && *p != '\0') { 574 *p++ = '\0'; 575 q = p; 576 while (*p != '\0' && curses_key < 0) { 577 if (escaped) { 578 escaped = FALSE; 579 curses_key = *p; 580 } else if (*p == '\\') { 581 escaped = TRUE; 582 } else if (modified) { 583 if (*p == '?') { 584 curses_key = ((modified == '^') 585 ? 127 586 : 255); 587 } else { 588 curses_key = ((modified == '^') 589 ? (*p & 0x1f) 590 : ((*p & 0x1f) | 0x80)); 591 } 592 } else if (*p == '^') { 593 modified = *p; 594 } else if (*p == '~') { 595 modified = *p; 596 } else if (isspace(UCH(*p))) { 597 break; 598 } 599 ++p; 600 } 601 if (!isspace(UCH(*p))) { 602 ; 603 } else { 604 *p++ = '\0'; 605 if (curses_key < 0) { 606 char fprefix[2]; 607 char check[2]; 608 int keynumber; 609 if (sscanf(q, "%[Ff]%d%c", fprefix, &keynumber, check) == 2) { 610 curses_key = KEY_F(keynumber); 611 is_function = TRUE; 612 } else { 613 for (xx = 0; xx < COUNT_CURSES; ++xx) { 614 if (!dlg_strcmp(curses_names[xx].name, q)) { 615 curses_key = curses_names[xx].code; 616 is_function = TRUE; 617 break; 618 } 619 } 620 } 621 } 622 } 623 q = skip_white(p); 624 p = skip_black(q); 625 if (p != q) { 626 for (xx = 0; xx < COUNT_DIALOG; ++xx) { 627 if (!dlg_strcmp(dialog_names[xx].name, q)) { 628 dialog_key = dialog_names[xx].code; 629 break; 630 } 631 } 632 } 633 if (*widget != '\0' 634 && curses_key >= 0 635 && dialog_key >= 0 636 && make_binding(widget, curses_key, is_function, dialog_key) != 0) { 637 result = TRUE; 638 } 639 } 640 return result; 641 } 642 643 static void 644 dump_curses_key(FILE *fp, int curses_key) 645 { 646 if (curses_key > KEY_MIN) { 647 unsigned n; 648 bool found = FALSE; 649 for (n = 0; n < COUNT_CURSES; ++n) { 650 if (curses_names[n].code == curses_key) { 651 fprintf(fp, "%s", curses_names[n].name); 652 found = TRUE; 653 break; 654 } 655 } 656 if (!found) { 657 if (curses_key >= KEY_F(0)) { 658 fprintf(fp, "F%d", curses_key - KEY_F(0)); 659 } else { 660 fprintf(fp, "curses%d", curses_key); 661 } 662 } 663 } else if (curses_key >= 0 && curses_key < 32) { 664 fprintf(fp, "^%c", curses_key + 64); 665 } else if (curses_key == 127) { 666 fprintf(fp, "^?"); 667 } else if (curses_key >= 128 && curses_key < 160) { 668 fprintf(fp, "~%c", curses_key - 64); 669 } else if (curses_key == 255) { 670 fprintf(fp, "~?"); 671 } else { 672 fprintf(fp, "\\%c", curses_key); 673 } 674 } 675 676 static void 677 dump_dialog_key(FILE *fp, int dialog_key) 678 { 679 unsigned n; 680 bool found = FALSE; 681 for (n = 0; n < COUNT_DIALOG; ++n) { 682 if (dialog_names[n].code == dialog_key) { 683 fputs(dialog_names[n].name, fp); 684 found = TRUE; 685 break; 686 } 687 } 688 if (!found) { 689 fprintf(fp, "dialog%d", dialog_key); 690 } 691 } 692 693 static void 694 dump_one_binding(FILE *fp, const char *widget, DLG_KEYS_BINDING * binding) 695 { 696 fprintf(fp, "bindkey %s ", widget); 697 dump_curses_key(fp, binding->curses_key); 698 fputc(' ', fp); 699 dump_dialog_key(fp, binding->dialog_key); 700 fputc('\n', fp); 701 } 702 703 void 704 dlg_dump_keys(FILE *fp) 705 { 706 LIST_BINDINGS *p; 707 const char *last = ""; 708 unsigned n; 709 unsigned count = 0; 710 711 for (p = all_bindings; p != 0; p = p->link) { 712 if (p->win == 0) { 713 ++count; 714 } 715 } 716 if (count != 0) { 717 for (p = all_bindings, n = 0; p != 0; p = p->link) { 718 if (p->win == 0) { 719 if (dlg_strcmp(last, p->name)) { 720 fprintf(fp, "\n# key bindings for %s widgets\n", 721 !strcmp(p->name, "*") ? "all" : p->name); 722 last = p->name; 723 } 724 dump_one_binding(fp, p->name, p->binding); 725 } 726 } 727 } 728 } 729 #endif /* HAVE_RC_FILE */ 730