1/*
2 *  $Id: dlg_keys.c,v 1.45 2018/05/28 17:27:10 tom Exp $
3 *
4 *  dlg_keys.c -- runtime binding support for dialog
5 *
6 *  Copyright 2006-2017,2018 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#define CHR_BACKSLASH   '\\'
30#define IsOctal(ch)     ((ch) >= '0' && (ch) <= '7')
31#define TableSize(name) (sizeof(name)/sizeof(name[0]))
32
33LIST_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 "*"
42static LIST_BINDINGS *all_bindings;
43static 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 */
48void
49dlg_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 */
89static int
90key_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 */
119void
120dlg_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 */
171void
172dlg_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 */
207int
208dlg_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 */
272int
273dlg_result_key(int dialog_key, int fkey GCC_UNUSED, int *resultp)
274{
275    int done = FALSE;
276
277#ifdef HAVE_RC_FILE
278    if (fkey) {
279	switch ((DLG_KEYS_ENUM) dialog_key) {
280	case DLGK_OK:
281	    *resultp = DLG_EXIT_OK;
282	    done = TRUE;
283	    break;
284	case DLGK_CANCEL:
285	    if (!dialog_vars.nocancel) {
286		*resultp = DLG_EXIT_CANCEL;
287		done = TRUE;
288	    }
289	    break;
290	case DLGK_EXTRA:
291	    if (dialog_vars.extra_button) {
292		*resultp = DLG_EXIT_EXTRA;
293		done = TRUE;
294	    }
295	    break;
296	case DLGK_HELP:
297	    if (dialog_vars.help_button) {
298		*resultp = DLG_EXIT_HELP;
299		done = TRUE;
300	    }
301	    break;
302	case DLGK_ESC:
303	    *resultp = DLG_EXIT_ESC;
304	    done = TRUE;
305	    break;
306	default:
307	    break;
308	}
309    } else
310#endif
311    if (dialog_key == ESC) {
312	*resultp = DLG_EXIT_ESC;
313	done = TRUE;
314    } else if (dialog_key == ERR) {
315	*resultp = DLG_EXIT_ERROR;
316	done = TRUE;
317    }
318
319    return done;
320}
321
322#ifdef HAVE_RC_FILE
323typedef struct {
324    const char *name;
325    int code;
326} CODENAME;
327
328#define ASCII_NAME(name,code)  { #name, code }
329#define CURSES_NAME(upper) { #upper, KEY_ ## upper }
330#define COUNT_CURSES  TableSize(curses_names)
331static const CODENAME curses_names[] =
332{
333    ASCII_NAME(ESC, '\033'),
334    ASCII_NAME(CR, '\r'),
335    ASCII_NAME(LF, '\n'),
336    ASCII_NAME(FF, '\f'),
337    ASCII_NAME(TAB, '\t'),
338    ASCII_NAME(DEL, '\177'),
339
340    CURSES_NAME(DOWN),
341    CURSES_NAME(UP),
342    CURSES_NAME(LEFT),
343    CURSES_NAME(RIGHT),
344    CURSES_NAME(HOME),
345    CURSES_NAME(BACKSPACE),
346    CURSES_NAME(F0),
347    CURSES_NAME(DL),
348    CURSES_NAME(IL),
349    CURSES_NAME(DC),
350    CURSES_NAME(IC),
351    CURSES_NAME(EIC),
352    CURSES_NAME(CLEAR),
353    CURSES_NAME(EOS),
354    CURSES_NAME(EOL),
355    CURSES_NAME(SF),
356    CURSES_NAME(SR),
357    CURSES_NAME(NPAGE),
358    CURSES_NAME(PPAGE),
359    CURSES_NAME(STAB),
360    CURSES_NAME(CTAB),
361    CURSES_NAME(CATAB),
362    CURSES_NAME(ENTER),
363    CURSES_NAME(PRINT),
364    CURSES_NAME(LL),
365    CURSES_NAME(A1),
366    CURSES_NAME(A3),
367    CURSES_NAME(B2),
368    CURSES_NAME(C1),
369    CURSES_NAME(C3),
370    CURSES_NAME(BTAB),
371    CURSES_NAME(BEG),
372    CURSES_NAME(CANCEL),
373    CURSES_NAME(CLOSE),
374    CURSES_NAME(COMMAND),
375    CURSES_NAME(COPY),
376    CURSES_NAME(CREATE),
377    CURSES_NAME(END),
378    CURSES_NAME(EXIT),
379    CURSES_NAME(FIND),
380    CURSES_NAME(HELP),
381    CURSES_NAME(MARK),
382    CURSES_NAME(MESSAGE),
383    CURSES_NAME(MOVE),
384    CURSES_NAME(NEXT),
385    CURSES_NAME(OPEN),
386    CURSES_NAME(OPTIONS),
387    CURSES_NAME(PREVIOUS),
388    CURSES_NAME(REDO),
389    CURSES_NAME(REFERENCE),
390    CURSES_NAME(REFRESH),
391    CURSES_NAME(REPLACE),
392    CURSES_NAME(RESTART),
393    CURSES_NAME(RESUME),
394    CURSES_NAME(SAVE),
395    CURSES_NAME(SBEG),
396    CURSES_NAME(SCANCEL),
397    CURSES_NAME(SCOMMAND),
398    CURSES_NAME(SCOPY),
399    CURSES_NAME(SCREATE),
400    CURSES_NAME(SDC),
401    CURSES_NAME(SDL),
402    CURSES_NAME(SELECT),
403    CURSES_NAME(SEND),
404    CURSES_NAME(SEOL),
405    CURSES_NAME(SEXIT),
406    CURSES_NAME(SFIND),
407    CURSES_NAME(SHELP),
408    CURSES_NAME(SHOME),
409    CURSES_NAME(SIC),
410    CURSES_NAME(SLEFT),
411    CURSES_NAME(SMESSAGE),
412    CURSES_NAME(SMOVE),
413    CURSES_NAME(SNEXT),
414    CURSES_NAME(SOPTIONS),
415    CURSES_NAME(SPREVIOUS),
416    CURSES_NAME(SPRINT),
417    CURSES_NAME(SREDO),
418    CURSES_NAME(SREPLACE),
419    CURSES_NAME(SRIGHT),
420    CURSES_NAME(SRSUME),
421    CURSES_NAME(SSAVE),
422    CURSES_NAME(SSUSPEND),
423    CURSES_NAME(SUNDO),
424    CURSES_NAME(SUSPEND),
425    CURSES_NAME(UNDO),
426};
427
428#define DIALOG_NAME(upper) { #upper, DLGK_ ## upper }
429#define COUNT_DIALOG  TableSize(dialog_names)
430static const CODENAME dialog_names[] =
431{
432    DIALOG_NAME(OK),
433    DIALOG_NAME(CANCEL),
434    DIALOG_NAME(EXTRA),
435    DIALOG_NAME(HELP),
436    DIALOG_NAME(ESC),
437    DIALOG_NAME(PAGE_FIRST),
438    DIALOG_NAME(PAGE_LAST),
439    DIALOG_NAME(PAGE_NEXT),
440    DIALOG_NAME(PAGE_PREV),
441    DIALOG_NAME(ITEM_FIRST),
442    DIALOG_NAME(ITEM_LAST),
443    DIALOG_NAME(ITEM_NEXT),
444    DIALOG_NAME(ITEM_PREV),
445    DIALOG_NAME(FIELD_FIRST),
446    DIALOG_NAME(FIELD_LAST),
447    DIALOG_NAME(FIELD_NEXT),
448    DIALOG_NAME(FIELD_PREV),
449    DIALOG_NAME(FORM_FIRST),
450    DIALOG_NAME(FORM_LAST),
451    DIALOG_NAME(FORM_NEXT),
452    DIALOG_NAME(FORM_PREV),
453    DIALOG_NAME(GRID_UP),
454    DIALOG_NAME(GRID_DOWN),
455    DIALOG_NAME(GRID_LEFT),
456    DIALOG_NAME(GRID_RIGHT),
457    DIALOG_NAME(DELETE_LEFT),
458    DIALOG_NAME(DELETE_RIGHT),
459    DIALOG_NAME(DELETE_ALL),
460    DIALOG_NAME(ENTER),
461    DIALOG_NAME(BEGIN),
462    DIALOG_NAME(FINAL),
463    DIALOG_NAME(SELECT),
464    DIALOG_NAME(HELPFILE),
465    DIALOG_NAME(TRACE),
466    DIALOG_NAME(TOGGLE)
467};
468
469#define MAP2(letter,actual) { letter, actual }
470
471static const struct {
472    int letter;
473    int actual;
474} escaped_letters[] = {
475
476    MAP2('a', DLG_CTRL('G')),
477	MAP2('b', DLG_CTRL('H')),
478	MAP2('f', DLG_CTRL('L')),
479	MAP2('n', DLG_CTRL('J')),
480	MAP2('r', DLG_CTRL('M')),
481	MAP2('s', CHR_SPACE),
482	MAP2('t', DLG_CTRL('I')),
483	MAP2('\\', '\\'),
484};
485
486#undef MAP2
487
488static char *
489skip_white(char *s)
490{
491    while (*s != '\0' && isspace(UCH(*s)))
492	++s;
493    return s;
494}
495
496static char *
497skip_black(char *s)
498{
499    while (*s != '\0' && !isspace(UCH(*s)))
500	++s;
501    return s;
502}
503
504/*
505 * Find a user-defined binding, given the curses key code.
506 */
507static DLG_KEYS_BINDING *
508find_binding(char *widget, int curses_key)
509{
510    LIST_BINDINGS *p;
511    DLG_KEYS_BINDING *result = 0;
512
513    for (p = all_bindings; p != 0; p = p->link) {
514	if (p->win == 0
515	    && !dlg_strcmp(p->name, widget)
516	    && p->binding->curses_key == curses_key) {
517	    result = p->binding;
518	    break;
519	}
520    }
521    return result;
522}
523
524/*
525 * Built-in bindings have a nonzero "win" member, and the associated binding
526 * table can have more than one entry.  We keep those last, since lookups will
527 * find the user-defined bindings first and use those.
528 *
529 * Sort "*" (all-widgets) entries past named widgets, since those are less
530 * specific.
531 */
532static int
533compare_bindings(LIST_BINDINGS * a, LIST_BINDINGS * b)
534{
535    int result = 0;
536    if (a->win == b->win) {
537	if (!strcmp(a->name, b->name)) {
538	    result = a->binding[0].curses_key - b->binding[0].curses_key;
539	} else if (!strcmp(b->name, WILDNAME)) {
540	    result = -1;
541	} else if (!strcmp(a->name, WILDNAME)) {
542	    result = 1;
543	} else {
544	    result = dlg_strcmp(a->name, b->name);
545	}
546    } else if (b->win) {
547	result = -1;
548    } else {
549	result = 1;
550    }
551    return result;
552}
553
554/*
555 * Find a user-defined binding, given the curses key code.  If it does not
556 * exist, create a new one, inserting it into the linked list, keeping it
557 * sorted to simplify lookups for user-defined bindings that can override
558 * the built-in bindings.
559 */
560static DLG_KEYS_BINDING *
561make_binding(char *widget, int curses_key, int is_function, int dialog_key)
562{
563    LIST_BINDINGS *entry = 0;
564    DLG_KEYS_BINDING *data = 0;
565    char *name;
566    LIST_BINDINGS *p, *q;
567    DLG_KEYS_BINDING *result = find_binding(widget, curses_key);
568
569    if (result == 0
570	&& (entry = dlg_calloc(LIST_BINDINGS, 1)) != 0
571	&& (data = dlg_calloc(DLG_KEYS_BINDING, 2)) != 0
572	&& (name = dlg_strclone(widget)) != 0) {
573
574	entry->name = name;
575	entry->binding = data;
576
577	data[0].is_function_key = is_function;
578	data[0].curses_key = curses_key;
579	data[0].dialog_key = dialog_key;
580
581	data[1] = end_keys_binding;
582
583	for (p = all_bindings, q = 0; p != 0; q = p, p = p->link) {
584	    if (compare_bindings(entry, p) < 0) {
585		break;
586	    }
587	}
588	if (q != 0) {
589	    q->link = entry;
590	} else {
591	    all_bindings = entry;
592	}
593	if (p != 0) {
594	    entry->link = p;
595	}
596	result = data;
597    } else if (entry != 0) {
598	free(entry);
599	if (data)
600	    free(data);
601    }
602
603    return result;
604}
605
606static int
607decode_escaped(char **string)
608{
609    unsigned n;
610    int result = 0;
611
612    if (IsOctal(**string)) {
613	int limit = 3;
614	while (limit-- > 0 && IsOctal(**string)) {
615	    int ch = (**string);
616	    *string += 1;
617	    result = (result << 3) | (ch - '0');
618	}
619    } else {
620	for (n = 0; n < TableSize(escaped_letters); ++n) {
621	    if (**string == escaped_letters[n].letter) {
622		*string += 1;
623		result = escaped_letters[n].actual;
624		break;
625	    }
626	}
627    }
628    return result;
629}
630
631static char *
632encode_escaped(int value)
633{
634    static char result[80];
635    unsigned n;
636    bool found = FALSE;
637    for (n = 0; n < TableSize(escaped_letters); ++n) {
638	if (value == escaped_letters[n].actual) {
639	    found = TRUE;
640	    sprintf(result, "%c", escaped_letters[n].letter);
641	    break;
642	}
643    }
644    if (!found) {
645	sprintf(result, "%03o", value & 0xff);
646    }
647    return result;
648}
649
650/*
651 * Parse the parameters of the "bindkey" configuration-file entry.  This
652 * expects widget name which may be "*", followed by curses key definition and
653 * then dialog key definition.
654 *
655 * The curses key "should" be one of the names (ignoring case) from
656 * curses_names[], but may also be a single control character (prefix "^" or
657 * "~" depending on whether it is C0 or C1), or an escaped single character.
658 * Binding a printable character with dialog is possible but not useful.
659 *
660 * The dialog key must be one of the names from dialog_names[].
661 */
662int
663dlg_parse_bindkey(char *params)
664{
665    char *p = skip_white(params);
666    char *q;
667    bool escaped = FALSE;
668    int modified = 0;
669    int result = FALSE;
670    unsigned xx;
671    char *widget;
672    int is_function = FALSE;
673    int curses_key;
674    int dialog_key;
675
676    curses_key = -1;
677    dialog_key = -1;
678    widget = p;
679
680    p = skip_black(p);
681    if (p != widget && *p != '\0') {
682	*p++ = '\0';
683	p = skip_white(p);
684	q = p;
685	while (*p != '\0' && curses_key < 0) {
686	    if (escaped) {
687		escaped = FALSE;
688		curses_key = decode_escaped(&p);
689	    } else if (*p == CHR_BACKSLASH) {
690		escaped = TRUE;
691	    } else if (modified) {
692		if (*p == '?') {
693		    curses_key = ((modified == '^')
694				  ? 127
695				  : 255);
696		} else {
697		    curses_key = ((modified == '^')
698				  ? (*p & 0x1f)
699				  : ((*p & 0x1f) | 0x80));
700		}
701	    } else if (*p == '^') {
702		modified = *p;
703	    } else if (*p == '~') {
704		modified = *p;
705	    } else if (isspace(UCH(*p))) {
706		break;
707	    }
708	    ++p;
709	}
710	if (!isspace(UCH(*p))) {
711	    ;
712	} else {
713	    *p++ = '\0';
714	    if (curses_key < 0) {
715		char fprefix[2];
716		char check[2];
717		int keynumber;
718		if (sscanf(q, "%[Ff]%d%c", fprefix, &keynumber, check) == 2) {
719		    curses_key = KEY_F(keynumber);
720		    is_function = TRUE;
721		} else {
722		    for (xx = 0; xx < COUNT_CURSES; ++xx) {
723			if (!dlg_strcmp(curses_names[xx].name, q)) {
724			    curses_key = curses_names[xx].code;
725			    is_function = (curses_key >= KEY_MIN);
726			    break;
727			}
728		    }
729		}
730	    }
731	}
732	q = skip_white(p);
733	p = skip_black(q);
734	if (p != q) {
735	    for (xx = 0; xx < COUNT_DIALOG; ++xx) {
736		if (!dlg_strcmp(dialog_names[xx].name, q)) {
737		    dialog_key = dialog_names[xx].code;
738		    break;
739		}
740	    }
741	}
742	if (*widget != '\0'
743	    && curses_key >= 0
744	    && dialog_key >= 0
745	    && make_binding(widget, curses_key, is_function, dialog_key) != 0) {
746	    result = TRUE;
747	}
748    }
749    return result;
750}
751
752static void
753dump_curses_key(FILE *fp, int curses_key)
754{
755    if (curses_key > KEY_MIN) {
756	unsigned n;
757	bool found = FALSE;
758	for (n = 0; n < COUNT_CURSES; ++n) {
759	    if (curses_names[n].code == curses_key) {
760		fprintf(fp, "%s", curses_names[n].name);
761		found = TRUE;
762		break;
763	    }
764	}
765	if (!found) {
766#ifdef KEY_MOUSE
767	    if (is_DLGK_MOUSE(curses_key)) {
768		fprintf(fp, "MOUSE-");
769		dump_curses_key(fp, curses_key - M_EVENT);
770	    } else
771#endif
772	    if (curses_key >= KEY_F(0)) {
773		fprintf(fp, "F%d", curses_key - KEY_F(0));
774	    } else {
775		fprintf(fp, "curses%d", curses_key);
776	    }
777	}
778    } else if (curses_key >= 0 && curses_key < 32) {
779	fprintf(fp, "^%c", curses_key + 64);
780    } else if (curses_key == 127) {
781	fprintf(fp, "^?");
782    } else if (curses_key >= 128 && curses_key < 160) {
783	fprintf(fp, "~%c", curses_key - 64);
784    } else if (curses_key == 255) {
785	fprintf(fp, "~?");
786    } else if (curses_key > 32 &&
787	       curses_key < 127 &&
788	       curses_key != CHR_BACKSLASH) {
789	fprintf(fp, "%c", curses_key);
790    } else {
791	fprintf(fp, "%c%s", CHR_BACKSLASH, encode_escaped(curses_key));
792    }
793}
794
795static void
796dump_dialog_key(FILE *fp, int dialog_key)
797{
798    unsigned n;
799    bool found = FALSE;
800    for (n = 0; n < COUNT_DIALOG; ++n) {
801	if (dialog_names[n].code == dialog_key) {
802	    fputs(dialog_names[n].name, fp);
803	    found = TRUE;
804	    break;
805	}
806    }
807    if (!found) {
808	fprintf(fp, "dialog%d", dialog_key);
809    }
810}
811
812static void
813dump_one_binding(FILE *fp,
814		 WINDOW *win,
815		 const char *widget,
816		 DLG_KEYS_BINDING * binding)
817{
818    int actual;
819    int fkey = (binding->curses_key > 255);
820
821    fprintf(fp, "bindkey %s ", widget);
822    dump_curses_key(fp, binding->curses_key);
823    fputc(' ', fp);
824    dump_dialog_key(fp, binding->dialog_key);
825    actual = dlg_lookup_key(win, binding->curses_key, &fkey);
826#ifdef KEY_MOUSE
827    if (is_DLGK_MOUSE(binding->curses_key) && is_DLGK_MOUSE(actual)) {
828	;			/* EMPTY */
829    } else
830#endif
831    if (actual != binding->dialog_key) {
832	fprintf(fp, "\t# overridden by ");
833	dump_dialog_key(fp, actual);
834    }
835    fputc('\n', fp);
836}
837
838/*
839 * Dump bindings for the given window.  If it is a null, then this dumps the
840 * initial bindings which were loaded from the rc-file that are used as
841 * overall defaults.
842 */
843void
844dlg_dump_window_keys(FILE *fp, WINDOW *win)
845{
846    if (fp != 0) {
847	LIST_BINDINGS *p;
848	DLG_KEYS_BINDING *q;
849	const char *last = "";
850
851	for (p = all_bindings; p != 0; p = p->link) {
852	    if (p->win == win) {
853		if (dlg_strcmp(last, p->name)) {
854		    fprintf(fp, "# key bindings for %s widgets%s\n",
855			    !strcmp(p->name, WILDNAME) ? "all" : p->name,
856			    win == 0 ? " (user-defined)" : "");
857		    last = p->name;
858		}
859		for (q = p->binding; q->is_function_key >= 0; ++q) {
860		    dump_one_binding(fp, win, p->name, q);
861		}
862	    }
863	}
864    }
865}
866
867/*
868 * Dump all of the bindings which are not specific to a given widget, i.e.,
869 * the "win" member is null.
870 */
871void
872dlg_dump_keys(FILE *fp)
873{
874    if (fp != 0) {
875	LIST_BINDINGS *p;
876	unsigned count = 0;
877
878	for (p = all_bindings; p != 0; p = p->link) {
879	    if (p->win == 0) {
880		++count;
881	    }
882	}
883	if (count != 0) {
884	    dlg_dump_window_keys(fp, 0);
885	}
886    }
887}
888#endif /* HAVE_RC_FILE */
889