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