1 /*
2 * $Id: dlg_keys.c,v 1.58 2020/11/26 17:11:56 Glenn.Herteg Exp $
3 *
4 * dlg_keys.c -- runtime binding support for dialog
5 *
6 * Copyright 2006-2019,2020 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 #include <dlg_internals.h>
27
28 #define LIST_BINDINGS struct _list_bindings
29
30 #define CHR_BACKSLASH '\\'
31 #define IsOctal(ch) ((ch) >= '0' && (ch) <= '7')
32
33 LIST_BINDINGS {
34 LIST_BINDINGS *link;
35 WINDOW *win; /* window on which widget gets input */
36 const char *name; /* widget name */
37 bool buttons; /* true only for dlg_register_buttons() */
38 DLG_KEYS_BINDING *binding; /* list of bindings */
39 };
40
41 #define WILDNAME "*"
42 static LIST_BINDINGS *all_bindings;
43 static const DLG_KEYS_BINDING end_keys_binding = END_KEYS_BINDING;
44
45 /*
46 * For a given named widget's window, associate a binding table.
47 */
48 void
dlg_register_window(WINDOW * win,const char * name,DLG_KEYS_BINDING * binding)49 dlg_register_window(WINDOW *win, const char *name, DLG_KEYS_BINDING * binding)
50 {
51 LIST_BINDINGS *p, *q;
52
53 for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
54 if (p->win == win && !strcmp(p->name, name)) {
55 p->binding = binding;
56 return;
57 }
58 }
59 /* add built-in bindings at the end of the list (see compare_bindings). */
60 if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
61 p->win = win;
62 p->name = name;
63 p->binding = binding;
64 if (q != 0) {
65 q->link = p;
66 } else {
67 all_bindings = p;
68 }
69 }
70 #if defined(HAVE_DLG_TRACE) && defined(HAVE_RC_FILE)
71 /*
72 * Trace the binding information assigned to this window. For most widgets
73 * there is only one binding table. forms have two, so the trace will be
74 * longer. Since compiled-in bindings are only visible when the widget is
75 * registered, there is no other way to see what bindings are available,
76 * than by running dialog and tracing it.
77 */
78 DLG_TRACE(("# dlg_register_window %s\n", name));
79 dlg_dump_keys(dialog_state.trace_output);
80 dlg_dump_window_keys(dialog_state.trace_output, win);
81 DLG_TRACE(("# ...done dlg_register_window %s\n", name));
82 #endif
83 }
84
85 /*
86 * Unlike dlg_lookup_key(), this looks for either widget-builtin or rc-file
87 * definitions, depending on whether 'win' is null.
88 */
89 static int
key_is_bound(WINDOW * win,const char * name,int curses_key,int function_key)90 key_is_bound(WINDOW *win, const char *name, int curses_key, int function_key)
91 {
92 LIST_BINDINGS *p;
93
94 for (p = all_bindings; p != 0; p = p->link) {
95 if (p->win == win && !dlg_strcmp(p->name, name)) {
96 int n;
97 for (n = 0; p->binding[n].is_function_key >= 0; ++n) {
98 if (p->binding[n].curses_key == curses_key
99 && p->binding[n].is_function_key == function_key) {
100 return TRUE;
101 }
102 }
103 }
104 }
105 return FALSE;
106 }
107
108 /*
109 * Call this function after dlg_register_window(), for the list of button
110 * labels associated with the widget.
111 *
112 * Ensure that dlg_lookup_key() will not accidentally translate a key that
113 * we would like to use for a button abbreviation to some other key, e.g.,
114 * h/j/k/l for navigation into a cursor key. Do this by binding the key
115 * to itself.
116 *
117 * See dlg_char_to_button().
118 */
119 void
dlg_register_buttons(WINDOW * win,const char * name,const char ** buttons)120 dlg_register_buttons(WINDOW *win, const char *name, const char **buttons)
121 {
122 int n;
123 LIST_BINDINGS *p;
124 DLG_KEYS_BINDING *q;
125
126 if (buttons == 0)
127 return;
128
129 for (n = 0; buttons[n] != 0; ++n) {
130 int curses_key = dlg_button_to_char(buttons[n]);
131
132 /* ignore binding if there is no key to bind */
133 if (curses_key < 0)
134 continue;
135
136 /* ignore multibyte characters */
137 if (curses_key >= KEY_MIN)
138 continue;
139
140 /* if it is not bound in the widget, skip it (no conflicts) */
141 if (!key_is_bound(win, name, curses_key, FALSE))
142 continue;
143
144 #ifdef HAVE_RC_FILE
145 /* if it is bound in the rc-file, skip it */
146 if (key_is_bound(0, name, curses_key, FALSE))
147 continue;
148 #endif
149
150 if ((p = dlg_calloc(LIST_BINDINGS, 1)) != 0) {
151 if ((q = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0) {
152 q[0].is_function_key = 0;
153 q[0].curses_key = curses_key;
154 q[0].dialog_key = curses_key;
155 q[1] = end_keys_binding;
156
157 p->win = win;
158 p->name = name;
159 p->buttons = TRUE;
160 p->binding = q;
161
162 /* put these at the beginning, to override the widget's table */
163 p->link = all_bindings;
164 all_bindings = p;
165 } else {
166 free(p);
167 }
168 }
169 }
170 }
171
172 /*
173 * Remove the bindings for a given window.
174 */
175 void
dlg_unregister_window(WINDOW * win)176 dlg_unregister_window(WINDOW *win)
177 {
178 LIST_BINDINGS *p, *q;
179
180 for (p = all_bindings, q = 0; p != 0; p = p->link) {
181 if (p->win == win) {
182 if (q != 0) {
183 q->link = p->link;
184 } else {
185 all_bindings = p->link;
186 }
187 /* the user-defined and buttons-bindings all are length=1 */
188 if (p->binding[1].is_function_key < 0)
189 free(p->binding);
190 free(p);
191 dlg_unregister_window(win);
192 break;
193 }
194 q = p;
195 }
196 }
197
198 /*
199 * Call this after wgetch(), using the same window pointer and passing
200 * the curses-key.
201 *
202 * If there is no binding associated with the widget, it simply returns
203 * the given curses-key.
204 *
205 * Parameters:
206 * win is the window on which the wgetch() was done.
207 * curses_key is the value returned by wgetch().
208 * fkey in/out (on input, it is nonzero if curses_key is a function key,
209 * and on output, it is nonzero if the result is a function key).
210 */
211 int
dlg_lookup_key(WINDOW * win,int curses_key,int * fkey)212 dlg_lookup_key(WINDOW *win, int curses_key, int *fkey)
213 {
214 LIST_BINDINGS *p;
215 DLG_KEYS_BINDING *q;
216
217 /*
218 * Ignore mouse clicks, since they are already encoded properly.
219 */
220 #ifdef KEY_MOUSE
221 if (*fkey != 0 && curses_key == KEY_MOUSE) {
222 ;
223 } else
224 #endif
225 /*
226 * Ignore resize events, since they are already encoded properly.
227 */
228 #ifdef KEY_RESIZE
229 if (*fkey != 0 && curses_key == KEY_RESIZE) {
230 ;
231 } else
232 #endif
233 if (*fkey == 0 || curses_key < KEY_MAX) {
234 const char *name = WILDNAME;
235 if (win != 0) {
236 for (p = all_bindings; p != 0; p = p->link) {
237 if (p->win == win) {
238 name = p->name;
239 break;
240 }
241 }
242 }
243 for (p = all_bindings; p != 0; p = p->link) {
244 if (p->win == win ||
245 (p->win == 0 &&
246 (!strcmp(p->name, name) || !strcmp(p->name, WILDNAME)))) {
247 int function_key = (*fkey != 0);
248 for (q = p->binding; q->is_function_key >= 0; ++q) {
249 if (p->buttons
250 && !function_key
251 && q->curses_key == (int) dlg_toupper(curses_key)) {
252 *fkey = 0;
253 return q->dialog_key;
254 }
255 if (q->curses_key == curses_key
256 && q->is_function_key == function_key) {
257 *fkey = q->dialog_key;
258 return *fkey;
259 }
260 }
261 }
262 }
263 }
264 return curses_key;
265 }
266
267 /*
268 * Test a dialog internal keycode to see if it corresponds to one of the push
269 * buttons on the widget such as "OK".
270 *
271 * This is only useful if there are user-defined key bindings, since there are
272 * no built-in bindings that map directly to DLGK_OK, etc.
273 *
274 * See also dlg_ok_buttoncode().
275 */
276 int
dlg_result_key(int dialog_key,int fkey GCC_UNUSED,int * resultp)277 dlg_result_key(int dialog_key, int fkey GCC_UNUSED, int *resultp)
278 {
279 int done = FALSE;
280
281 DLG_TRACE(("# dlg_result_key(dialog_key=%d, fkey=%d)\n", dialog_key, fkey));
282 #ifdef KEY_RESIZE
283 if (dialog_state.had_resize) {
284 if (dialog_key == ERR) {
285 dialog_key = 0;
286 } else {
287 dialog_state.had_resize = FALSE;
288 }
289 } else if (fkey && dialog_key == KEY_RESIZE) {
290 dialog_state.had_resize = TRUE;
291 }
292 #endif
293 #ifdef HAVE_RC_FILE
294 if (fkey) {
295 switch ((DLG_KEYS_ENUM) dialog_key) {
296 case DLGK_OK:
297 if (!dialog_vars.nook) {
298 *resultp = DLG_EXIT_OK;
299 done = TRUE;
300 }
301 break;
302 case DLGK_CANCEL:
303 if (!dialog_vars.nocancel) {
304 *resultp = DLG_EXIT_CANCEL;
305 done = TRUE;
306 }
307 break;
308 case DLGK_EXTRA:
309 if (dialog_vars.extra_button) {
310 *resultp = DLG_EXIT_EXTRA;
311 done = TRUE;
312 }
313 break;
314 case DLGK_HELP:
315 if (dialog_vars.help_button) {
316 *resultp = DLG_EXIT_HELP;
317 done = TRUE;
318 }
319 break;
320 case DLGK_ESC:
321 *resultp = DLG_EXIT_ESC;
322 done = TRUE;
323 break;
324 default:
325 break;
326 }
327 } else
328 #endif
329 if (dialog_key == ESC) {
330 *resultp = DLG_EXIT_ESC;
331 done = TRUE;
332 } else if (dialog_key == ERR) {
333 *resultp = DLG_EXIT_ERROR;
334 done = TRUE;
335 }
336
337 return done;
338 }
339
340 /*
341 * If a key was bound to one of the button-codes in dlg_result_key(), fake
342 * a button-value and an "Enter" key to cause the calling widget to return
343 * the corresponding status.
344 *
345 * See dlg_ok_buttoncode(), which maps settings for ok/extra/help and button
346 * number into exit-code.
347 */
348 int
dlg_button_key(int exit_code,int * button,int * dialog_key,int * fkey)349 dlg_button_key(int exit_code, int *button, int *dialog_key, int *fkey)
350 {
351 int changed = FALSE;
352 switch (exit_code) {
353 case DLG_EXIT_OK:
354 if (!dialog_vars.nook) {
355 *button = 0;
356 changed = TRUE;
357 }
358 break;
359 case DLG_EXIT_EXTRA:
360 if (dialog_vars.extra_button) {
361 *button = dialog_vars.nook ? 0 : 1;
362 changed = TRUE;
363 }
364 break;
365 case DLG_EXIT_CANCEL:
366 if (!dialog_vars.nocancel) {
367 *button = dialog_vars.nook ? 1 : 2;
368 changed = TRUE;
369 }
370 break;
371 case DLG_EXIT_HELP:
372 if (dialog_vars.help_button) {
373 int cancel = dialog_vars.nocancel ? 0 : 1;
374 int extra = dialog_vars.extra_button ? 1 : 0;
375 int okay = dialog_vars.nook ? 0 : 1;
376 *button = okay + extra + cancel;
377 changed = TRUE;
378 }
379 break;
380 }
381 if (changed) {
382 DLG_TRACE(("# dlg_button_key(%d:%s) button %d\n",
383 exit_code, dlg_exitcode2s(exit_code), *button));
384 *dialog_key = *fkey = DLGK_ENTER;
385 }
386 return changed;
387 }
388
389 int
dlg_ok_button_key(int exit_code,int * button,int * dialog_key,int * fkey)390 dlg_ok_button_key(int exit_code, int *button, int *dialog_key, int *fkey)
391 {
392 int result;
393 DIALOG_VARS save;
394
395 dlg_save_vars(&save);
396 dialog_vars.nocancel = TRUE;
397
398 result = dlg_button_key(exit_code, button, dialog_key, fkey);
399
400 dlg_restore_vars(&save);
401 return result;
402 }
403
404 #ifdef HAVE_RC_FILE
405 typedef struct {
406 const char *name;
407 int code;
408 } CODENAME;
409
410 #define ASCII_NAME(name,code) { #name, code }
411 #define CURSES_NAME(upper) { #upper, KEY_ ## upper }
412 #define COUNT_CURSES TableSize(curses_names)
413 static const CODENAME curses_names[] =
414 {
415 ASCII_NAME(ESC, '\033'),
416 ASCII_NAME(CR, '\r'),
417 ASCII_NAME(LF, '\n'),
418 ASCII_NAME(FF, '\f'),
419 ASCII_NAME(TAB, '\t'),
420 ASCII_NAME(DEL, '\177'),
421
422 CURSES_NAME(DOWN),
423 CURSES_NAME(UP),
424 CURSES_NAME(LEFT),
425 CURSES_NAME(RIGHT),
426 CURSES_NAME(HOME),
427 CURSES_NAME(BACKSPACE),
428 CURSES_NAME(F0),
429 CURSES_NAME(DL),
430 CURSES_NAME(IL),
431 CURSES_NAME(DC),
432 CURSES_NAME(IC),
433 CURSES_NAME(EIC),
434 CURSES_NAME(CLEAR),
435 CURSES_NAME(EOS),
436 CURSES_NAME(EOL),
437 CURSES_NAME(SF),
438 CURSES_NAME(SR),
439 CURSES_NAME(NPAGE),
440 CURSES_NAME(PPAGE),
441 CURSES_NAME(STAB),
442 CURSES_NAME(CTAB),
443 CURSES_NAME(CATAB),
444 CURSES_NAME(ENTER),
445 CURSES_NAME(PRINT),
446 CURSES_NAME(LL),
447 CURSES_NAME(A1),
448 CURSES_NAME(A3),
449 CURSES_NAME(B2),
450 CURSES_NAME(C1),
451 CURSES_NAME(C3),
452 CURSES_NAME(BTAB),
453 CURSES_NAME(BEG),
454 CURSES_NAME(CANCEL),
455 CURSES_NAME(CLOSE),
456 CURSES_NAME(COMMAND),
457 CURSES_NAME(COPY),
458 CURSES_NAME(CREATE),
459 CURSES_NAME(END),
460 CURSES_NAME(EXIT),
461 CURSES_NAME(FIND),
462 CURSES_NAME(HELP),
463 CURSES_NAME(MARK),
464 CURSES_NAME(MESSAGE),
465 CURSES_NAME(MOVE),
466 CURSES_NAME(NEXT),
467 CURSES_NAME(OPEN),
468 CURSES_NAME(OPTIONS),
469 CURSES_NAME(PREVIOUS),
470 CURSES_NAME(REDO),
471 CURSES_NAME(REFERENCE),
472 CURSES_NAME(REFRESH),
473 CURSES_NAME(REPLACE),
474 CURSES_NAME(RESTART),
475 CURSES_NAME(RESUME),
476 CURSES_NAME(SAVE),
477 CURSES_NAME(SBEG),
478 CURSES_NAME(SCANCEL),
479 CURSES_NAME(SCOMMAND),
480 CURSES_NAME(SCOPY),
481 CURSES_NAME(SCREATE),
482 CURSES_NAME(SDC),
483 CURSES_NAME(SDL),
484 CURSES_NAME(SELECT),
485 CURSES_NAME(SEND),
486 CURSES_NAME(SEOL),
487 CURSES_NAME(SEXIT),
488 CURSES_NAME(SFIND),
489 CURSES_NAME(SHELP),
490 CURSES_NAME(SHOME),
491 CURSES_NAME(SIC),
492 CURSES_NAME(SLEFT),
493 CURSES_NAME(SMESSAGE),
494 CURSES_NAME(SMOVE),
495 CURSES_NAME(SNEXT),
496 CURSES_NAME(SOPTIONS),
497 CURSES_NAME(SPREVIOUS),
498 CURSES_NAME(SPRINT),
499 CURSES_NAME(SREDO),
500 CURSES_NAME(SREPLACE),
501 CURSES_NAME(SRIGHT),
502 CURSES_NAME(SRSUME),
503 CURSES_NAME(SSAVE),
504 CURSES_NAME(SSUSPEND),
505 CURSES_NAME(SUNDO),
506 CURSES_NAME(SUSPEND),
507 CURSES_NAME(UNDO),
508 };
509
510 #define DIALOG_NAME(upper) { #upper, DLGK_ ## upper }
511 #define COUNT_DIALOG TableSize(dialog_names)
512 static const CODENAME dialog_names[] =
513 {
514 DIALOG_NAME(OK),
515 DIALOG_NAME(CANCEL),
516 DIALOG_NAME(EXTRA),
517 DIALOG_NAME(HELP),
518 DIALOG_NAME(ESC),
519 DIALOG_NAME(PAGE_FIRST),
520 DIALOG_NAME(PAGE_LAST),
521 DIALOG_NAME(PAGE_NEXT),
522 DIALOG_NAME(PAGE_PREV),
523 DIALOG_NAME(ITEM_FIRST),
524 DIALOG_NAME(ITEM_LAST),
525 DIALOG_NAME(ITEM_NEXT),
526 DIALOG_NAME(ITEM_PREV),
527 DIALOG_NAME(FIELD_FIRST),
528 DIALOG_NAME(FIELD_LAST),
529 DIALOG_NAME(FIELD_NEXT),
530 DIALOG_NAME(FIELD_PREV),
531 DIALOG_NAME(FORM_FIRST),
532 DIALOG_NAME(FORM_LAST),
533 DIALOG_NAME(FORM_NEXT),
534 DIALOG_NAME(FORM_PREV),
535 DIALOG_NAME(GRID_UP),
536 DIALOG_NAME(GRID_DOWN),
537 DIALOG_NAME(GRID_LEFT),
538 DIALOG_NAME(GRID_RIGHT),
539 DIALOG_NAME(DELETE_LEFT),
540 DIALOG_NAME(DELETE_RIGHT),
541 DIALOG_NAME(DELETE_ALL),
542 DIALOG_NAME(ENTER),
543 DIALOG_NAME(BEGIN),
544 DIALOG_NAME(FINAL),
545 DIALOG_NAME(SELECT),
546 DIALOG_NAME(HELPFILE),
547 DIALOG_NAME(TRACE),
548 DIALOG_NAME(TOGGLE),
549 DIALOG_NAME(LEAVE)
550 };
551
552 #define MAP2(letter,actual) { letter, actual }
553
554 static const struct {
555 int letter;
556 int actual;
557 } escaped_letters[] = {
558
559 MAP2('a', DLG_CTRL('G')),
560 MAP2('b', DLG_CTRL('H')),
561 MAP2('f', DLG_CTRL('L')),
562 MAP2('n', DLG_CTRL('J')),
563 MAP2('r', DLG_CTRL('M')),
564 MAP2('s', CHR_SPACE),
565 MAP2('t', DLG_CTRL('I')),
566 MAP2('\\', '\\'),
567 };
568
569 #undef MAP2
570
571 static char *
skip_white(char * s)572 skip_white(char *s)
573 {
574 while (*s != '\0' && isspace(UCH(*s)))
575 ++s;
576 return s;
577 }
578
579 static char *
skip_black(char * s)580 skip_black(char *s)
581 {
582 while (*s != '\0' && !isspace(UCH(*s)))
583 ++s;
584 return s;
585 }
586
587 /*
588 * Find a user-defined binding, given the curses key code.
589 */
590 static DLG_KEYS_BINDING *
find_binding(char * widget,int curses_key)591 find_binding(char *widget, int curses_key)
592 {
593 LIST_BINDINGS *p;
594 DLG_KEYS_BINDING *result = 0;
595
596 for (p = all_bindings; p != 0; p = p->link) {
597 if (p->win == 0
598 && !dlg_strcmp(p->name, widget)
599 && p->binding->curses_key == curses_key) {
600 result = p->binding;
601 break;
602 }
603 }
604 return result;
605 }
606
607 /*
608 * Built-in bindings have a nonzero "win" member, and the associated binding
609 * table can have more than one entry. We keep those last, since lookups will
610 * find the user-defined bindings first and use those.
611 *
612 * Sort "*" (all-widgets) entries past named widgets, since those are less
613 * specific.
614 */
615 static int
compare_bindings(LIST_BINDINGS * a,LIST_BINDINGS * b)616 compare_bindings(LIST_BINDINGS * a, LIST_BINDINGS * b)
617 {
618 int result = 0;
619 if (a->win == b->win) {
620 if (!strcmp(a->name, b->name)) {
621 result = a->binding[0].curses_key - b->binding[0].curses_key;
622 } else if (!strcmp(b->name, WILDNAME)) {
623 result = -1;
624 } else if (!strcmp(a->name, WILDNAME)) {
625 result = 1;
626 } else {
627 result = dlg_strcmp(a->name, b->name);
628 }
629 } else if (b->win) {
630 result = -1;
631 } else {
632 result = 1;
633 }
634 return result;
635 }
636
637 /*
638 * Find a user-defined binding, given the curses key code. If it does not
639 * exist, create a new one, inserting it into the linked list, keeping it
640 * sorted to simplify lookups for user-defined bindings that can override
641 * the built-in bindings.
642 */
643 static DLG_KEYS_BINDING *
make_binding(char * widget,int curses_key,int is_function,int dialog_key)644 make_binding(char *widget, int curses_key, int is_function, int dialog_key)
645 {
646 LIST_BINDINGS *entry = 0;
647 DLG_KEYS_BINDING *data = 0;
648 char *name;
649 DLG_KEYS_BINDING *result = find_binding(widget, curses_key);
650
651 if (result == 0
652 && (entry = dlg_calloc(LIST_BINDINGS, 1)) != 0
653 && (data = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0
654 && (name = dlg_strclone(widget)) != 0) {
655 LIST_BINDINGS *p, *q;
656
657 entry->name = name;
658 entry->binding = data;
659
660 data[0].is_function_key = is_function;
661 data[0].curses_key = curses_key;
662 data[0].dialog_key = dialog_key;
663
664 data[1] = end_keys_binding;
665
666 for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
667 if (compare_bindings(entry, p) < 0) {
668 break;
669 }
670 }
671 if (q != 0) {
672 q->link = entry;
673 } else {
674 all_bindings = entry;
675 }
676 if (p != 0) {
677 entry->link = p;
678 }
679 result = data;
680 } else if (entry != 0) {
681 free(entry);
682 if (data)
683 free(data);
684 }
685
686 return result;
687 }
688
689 static int
decode_escaped(char ** string)690 decode_escaped(char **string)
691 {
692 int result = 0;
693
694 if (IsOctal(**string)) {
695 int limit = 3;
696 while (limit-- > 0 && IsOctal(**string)) {
697 int ch = (**string);
698 *string += 1;
699 result = (result << 3) | (ch - '0');
700 }
701 } else {
702 unsigned n;
703
704 for (n = 0; n < TableSize(escaped_letters); ++n) {
705 if (**string == escaped_letters[n].letter) {
706 *string += 1;
707 result = escaped_letters[n].actual;
708 break;
709 }
710 }
711 }
712 return result;
713 }
714
715 static char *
encode_escaped(int value)716 encode_escaped(int value)
717 {
718 static char result[80];
719 unsigned n;
720 bool found = FALSE;
721 for (n = 0; n < TableSize(escaped_letters); ++n) {
722 if (value == escaped_letters[n].actual) {
723 found = TRUE;
724 sprintf(result, "%c", escaped_letters[n].letter);
725 break;
726 }
727 }
728 if (!found) {
729 sprintf(result, "%03o", value & 0xff);
730 }
731 return result;
732 }
733
734 /*
735 * Parse the parameters of the "bindkey" configuration-file entry. This
736 * expects widget name which may be "*", followed by curses key definition and
737 * then dialog key definition.
738 *
739 * The curses key "should" be one of the names (ignoring case) from
740 * curses_names[], but may also be a single control character (prefix "^" or
741 * "~" depending on whether it is C0 or C1), or an escaped single character.
742 * Binding a printable character with dialog is possible but not useful.
743 *
744 * The dialog key must be one of the names from dialog_names[].
745 */
746 int
dlg_parse_bindkey(char * params)747 dlg_parse_bindkey(char *params)
748 {
749 char *p = skip_white(params);
750 int result = FALSE;
751 char *widget;
752 int curses_key;
753 int dialog_key;
754
755 curses_key = -1;
756 dialog_key = -1;
757 widget = p;
758
759 p = skip_black(p);
760 if (p != widget && *p != '\0') {
761 char *q;
762 unsigned xx;
763 bool escaped = FALSE;
764 int modified = 0;
765 int is_function = FALSE;
766
767 *p++ = '\0';
768 p = skip_white(p);
769 q = p;
770 while (*p != '\0' && curses_key < 0) {
771 if (escaped) {
772 escaped = FALSE;
773 curses_key = decode_escaped(&p);
774 } else if (*p == CHR_BACKSLASH) {
775 escaped = TRUE;
776 } else if (modified) {
777 if (*p == '?') {
778 curses_key = ((modified == '^')
779 ? 127
780 : 255);
781 } else {
782 curses_key = ((modified == '^')
783 ? (*p & 0x1f)
784 : ((*p & 0x1f) | 0x80));
785 }
786 } else if (*p == '^') {
787 modified = *p;
788 } else if (*p == '~') {
789 modified = *p;
790 } else if (isspace(UCH(*p))) {
791 break;
792 }
793 ++p;
794 }
795 if (!isspace(UCH(*p))) {
796 ;
797 } else {
798 *p++ = '\0';
799 if (curses_key < 0) {
800 char fprefix[2];
801 char check[2];
802 int keynumber;
803 if (sscanf(q, "%1[Ff]%d%c", fprefix, &keynumber, check) == 2) {
804 curses_key = KEY_F(keynumber);
805 is_function = TRUE;
806 } else {
807 for (xx = 0; xx < COUNT_CURSES; ++xx) {
808 if (!dlg_strcmp(curses_names[xx].name, q)) {
809 curses_key = curses_names[xx].code;
810 is_function = (curses_key >= KEY_MIN);
811 break;
812 }
813 }
814 }
815 }
816 }
817 q = skip_white(p);
818 p = skip_black(q);
819 if (p != q) {
820 for (xx = 0; xx < COUNT_DIALOG; ++xx) {
821 if (!dlg_strcmp(dialog_names[xx].name, q)) {
822 dialog_key = dialog_names[xx].code;
823 break;
824 }
825 }
826 }
827 if (*widget != '\0'
828 && curses_key >= 0
829 && dialog_key >= 0
830 && make_binding(widget, curses_key, is_function, dialog_key) != 0) {
831 result = TRUE;
832 }
833 }
834 return result;
835 }
836
837 static void
dump_curses_key(FILE * fp,int curses_key)838 dump_curses_key(FILE *fp, int curses_key)
839 {
840 if (curses_key > KEY_MIN) {
841 unsigned n;
842 bool found = FALSE;
843 for (n = 0; n < COUNT_CURSES; ++n) {
844 if (curses_names[n].code == curses_key) {
845 fprintf(fp, "%s", curses_names[n].name);
846 found = TRUE;
847 break;
848 }
849 }
850 if (!found) {
851 #ifdef KEY_MOUSE
852 if (is_DLGK_MOUSE(curses_key)) {
853 fprintf(fp, "MOUSE-");
854 dump_curses_key(fp, curses_key - M_EVENT);
855 } else
856 #endif
857 if (curses_key >= KEY_F(0)) {
858 fprintf(fp, "F%d", curses_key - KEY_F(0));
859 } else {
860 fprintf(fp, "curses%d", curses_key);
861 }
862 }
863 } else if (curses_key >= 0 && curses_key < 32) {
864 fprintf(fp, "^%c", curses_key + 64);
865 } else if (curses_key == 127) {
866 fprintf(fp, "^?");
867 } else if (curses_key >= 128 && curses_key < 160) {
868 fprintf(fp, "~%c", curses_key - 64);
869 } else if (curses_key == 255) {
870 fprintf(fp, "~?");
871 } else if (curses_key > 32 &&
872 curses_key < 127 &&
873 curses_key != CHR_BACKSLASH) {
874 fprintf(fp, "%c", curses_key);
875 } else {
876 fprintf(fp, "%c%s", CHR_BACKSLASH, encode_escaped(curses_key));
877 }
878 }
879
880 static void
dump_dialog_key(FILE * fp,int dialog_key)881 dump_dialog_key(FILE *fp, int dialog_key)
882 {
883 unsigned n;
884 bool found = FALSE;
885 for (n = 0; n < COUNT_DIALOG; ++n) {
886 if (dialog_names[n].code == dialog_key) {
887 fputs(dialog_names[n].name, fp);
888 found = TRUE;
889 break;
890 }
891 }
892 if (!found) {
893 fprintf(fp, "dialog%d", dialog_key);
894 }
895 }
896
897 static void
dump_one_binding(FILE * fp,WINDOW * win,const char * widget,DLG_KEYS_BINDING * binding)898 dump_one_binding(FILE *fp,
899 WINDOW *win,
900 const char *widget,
901 DLG_KEYS_BINDING * binding)
902 {
903 int actual;
904 int fkey = (binding->curses_key > 255);
905
906 fprintf(fp, "bindkey %s ", widget);
907 dump_curses_key(fp, binding->curses_key);
908 fputc(' ', fp);
909 dump_dialog_key(fp, binding->dialog_key);
910 actual = dlg_lookup_key(win, binding->curses_key, &fkey);
911 #ifdef KEY_MOUSE
912 if (is_DLGK_MOUSE(binding->curses_key) && is_DLGK_MOUSE(actual)) {
913 ; /* EMPTY */
914 } else
915 #endif
916 if (actual != binding->dialog_key) {
917 fprintf(fp, "\t# overridden by ");
918 dump_dialog_key(fp, actual);
919 }
920 fputc('\n', fp);
921 }
922
923 /*
924 * Dump bindings for the given window. If it is a null, then this dumps the
925 * initial bindings which were loaded from the rc-file that are used as
926 * overall defaults.
927 */
928 void
dlg_dump_window_keys(FILE * fp,WINDOW * win)929 dlg_dump_window_keys(FILE *fp, WINDOW *win)
930 {
931 if (fp != 0) {
932 LIST_BINDINGS *p;
933 DLG_KEYS_BINDING *q;
934 const char *last = "";
935
936 for (p = all_bindings; p != 0; p = p->link) {
937 if (p->win == win) {
938 if (dlg_strcmp(last, p->name)) {
939 fprintf(fp, "# key bindings for %s widgets%s\n",
940 !strcmp(p->name, WILDNAME) ? "all" : p->name,
941 win == 0 ? " (user-defined)" : "");
942 last = p->name;
943 }
944 for (q = p->binding; q->is_function_key >= 0; ++q) {
945 dump_one_binding(fp, win, p->name, q);
946 }
947 }
948 }
949 }
950 }
951
952 /*
953 * Dump all of the bindings which are not specific to a given widget, i.e.,
954 * the "win" member is null.
955 */
956 void
dlg_dump_keys(FILE * fp)957 dlg_dump_keys(FILE *fp)
958 {
959 if (fp != 0) {
960 LIST_BINDINGS *p;
961 unsigned count = 0;
962
963 for (p = all_bindings; p != 0; p = p->link) {
964 if (p->win == 0) {
965 ++count;
966 }
967 }
968 if (count != 0) {
969 dlg_dump_window_keys(fp, 0);
970 }
971 }
972 }
973 #endif /* HAVE_RC_FILE */
974