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
dlg_register_window(WINDOW * win,const char * name,DLG_KEYS_BINDING * binding)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
key_is_bound(WINDOW * win,const char * name,int curses_key,int function_key)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
dlg_register_buttons(WINDOW * win,const char * name,const char ** buttons)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
dlg_unregister_window(WINDOW * win)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
dlg_lookup_key(WINDOW * win,int curses_key,int * fkey)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
dlg_result_key(int dialog_key,int fkey GCC_UNUSED,int * resultp)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 *
skip_white(char * s)425 skip_white(char *s)
426 {
427 while (*s != '\0' && isspace(UCH(*s)))
428 ++s;
429 return s;
430 }
431
432 static char *
skip_black(char * s)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 *
find_binding(char * widget,int curses_key)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
compare_bindings(LIST_BINDINGS * a,LIST_BINDINGS * b)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 *
make_binding(char * widget,int curses_key,int is_function,int dialog_key)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
dlg_parse_bindkey(char * params)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
dump_curses_key(FILE * fp,int curses_key)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
dump_dialog_key(FILE * fp,int dialog_key)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
dump_one_binding(FILE * fp,const char * widget,DLG_KEYS_BINDING * binding)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
dlg_dump_keys(FILE * fp)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