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
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 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 */
175void
176dlg_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 */
211int
212dlg_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 */
276int
277dlg_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 */
348int
349dlg_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
389int
390dlg_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
405typedef 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)
413static 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)
512static 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
554static 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
571static char *
572skip_white(char *s)
573{
574    while (*s != '\0' && isspace(UCH(*s)))
575	++s;
576    return s;
577}
578
579static char *
580skip_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 */
590static DLG_KEYS_BINDING *
591find_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 */
615static int
616compare_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 */
643static DLG_KEYS_BINDING *
644make_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
689static int
690decode_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
715static char *
716encode_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 */
746int
747dlg_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
837static void
838dump_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
880static void
881dump_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
897static void
898dump_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 */
928void
929dlg_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 */
956void
957dlg_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