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