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