1 /*
2  * GNT - The GLib Ncurses Toolkit
3  *
4  * GNT is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This library is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  */
22 
23 #include <string.h>
24 
25 #include "gntinternal.h"
26 #undef GNT_LOG_DOMAIN
27 #define GNT_LOG_DOMAIN "Bindable"
28 
29 #include "gntbindable.h"
30 #include "gntstyle.h"
31 #include "gnt.h"
32 #include "gntutils.h"
33 #include "gnttextview.h"
34 #include "gnttree.h"
35 #include "gntbox.h"
36 #include "gntbutton.h"
37 #include "gntwindow.h"
38 #include "gntlabel.h"
39 
40 static GObjectClass *parent_class = NULL;
41 
42 static struct
43 {
44 	char * okeys;         /* Old keystrokes */
45 	char * keys;          /* New Keystrokes being bound to the action */
46 	GntBindableClass * klass; /* Class of the object that's getting keys rebound */
47 	char * name;          /* The name of the action */
48 	GList * params;       /* The list of paramaters */
49 } rebind_info;
50 
51 static void
gnt_bindable_free_rebind_info(void)52 gnt_bindable_free_rebind_info(void)
53 {
54 	g_free(rebind_info.name);
55 	g_free(rebind_info.keys);
56 	g_free(rebind_info.okeys);
57 }
58 
59 static void
gnt_bindable_rebinding_cancel(GntWidget * button,gpointer data)60 gnt_bindable_rebinding_cancel(GntWidget *button, gpointer data)
61 {
62 	gnt_bindable_free_rebind_info();
63 	gnt_widget_destroy(GNT_WIDGET(data));
64 }
65 
66 static void
gnt_bindable_rebinding_rebind(GntWidget * button,gpointer data)67 gnt_bindable_rebinding_rebind(GntWidget *button, gpointer data)
68 {
69 	if (rebind_info.keys) {
70 		gnt_bindable_register_binding(rebind_info.klass,
71 				NULL,
72 				rebind_info.okeys,
73 				rebind_info.params);
74 		gnt_bindable_register_binding(rebind_info.klass,
75 				rebind_info.name,
76 				rebind_info.keys,
77 				rebind_info.params);
78 	}
79 	gnt_bindable_free_rebind_info();
80 	gnt_widget_destroy(GNT_WIDGET(data));
81 }
82 
83 static gboolean
gnt_bindable_rebinding_grab_key(GntBindable * bindable,const char * text,gpointer data)84 gnt_bindable_rebinding_grab_key(GntBindable *bindable, const char *text, gpointer data)
85 {
86 	GntTextView *textview = GNT_TEXT_VIEW(data);
87 	char *new_text;
88 	const char *tmp;
89 
90 	if (text && *text) {
91 		/* Rebinding tab or enter for something is probably not that great an idea */
92 		if (!strcmp(text, GNT_KEY_CTRL_I) || !strcmp(text, GNT_KEY_ENTER)) {
93 			return FALSE;
94 		}
95 
96 		tmp = gnt_key_lookup(text);
97 		new_text = g_strdup_printf("KEY: \"%s\"", tmp);
98 		gnt_text_view_clear(textview);
99 		gnt_text_view_append_text_with_flags(textview, new_text, GNT_TEXT_FLAG_NORMAL);
100 		g_free(new_text);
101 
102 		g_free(rebind_info.keys);
103 		rebind_info.keys = g_strdup(text);
104 
105 		return TRUE;
106 	}
107 	return FALSE;
108 }
109 static void
gnt_bindable_rebinding_activate(GntBindable * data,gpointer bindable)110 gnt_bindable_rebinding_activate(GntBindable *data, gpointer bindable)
111 {
112 	const char *widget_name = g_type_name(G_OBJECT_TYPE(bindable));
113 	char *keys;
114 	GntWidget *key_textview;
115 	GntWidget *label;
116 	GntWidget *bind_button, *cancel_button;
117 	GntWidget *button_box;
118 	GList *current_row_data;
119 	char *tmp;
120 	GntWidget *win = gnt_window_new();
121 	GntTree *tree = GNT_TREE(data);
122 	GntWidget *vbox = gnt_box_new(FALSE, TRUE);
123 
124 	rebind_info.klass = GNT_BINDABLE_GET_CLASS(bindable);
125 
126 	current_row_data = gnt_tree_get_selection_text_list(tree);
127 	rebind_info.name = g_strdup(g_list_nth_data(current_row_data, 1));
128 
129 	keys = gnt_tree_get_selection_data(tree);
130 	rebind_info.okeys = g_strdup(gnt_key_translate(keys));
131 
132 	rebind_info.params = NULL;
133 
134 	g_list_foreach(current_row_data, (GFunc)g_free, NULL);
135 	g_list_free(current_row_data);
136 
137 	gnt_box_set_alignment(GNT_BOX(vbox), GNT_ALIGN_MID);
138 
139 	gnt_box_set_title(GNT_BOX(win), "Key Capture");
140 
141 	tmp = g_strdup_printf("Type the new bindings for %s in a %s.", rebind_info.name, widget_name);
142 	label = gnt_label_new(tmp);
143 	g_free(tmp);
144 	gnt_box_add_widget(GNT_BOX(vbox), label);
145 
146 	tmp = g_strdup_printf("KEY: \"%s\"", keys);
147 	key_textview = gnt_text_view_new();
148 	gnt_widget_set_size(key_textview, key_textview->priv.x, 2);
149 	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(key_textview), tmp, GNT_TEXT_FLAG_NORMAL);
150 	g_free(tmp);
151 	gnt_widget_set_name(key_textview, "keystroke");
152 	gnt_box_add_widget(GNT_BOX(vbox), key_textview);
153 
154 	g_signal_connect(G_OBJECT(win), "key_pressed", G_CALLBACK(gnt_bindable_rebinding_grab_key), key_textview);
155 
156 	button_box = gnt_box_new(FALSE, FALSE);
157 
158 	bind_button = gnt_button_new("BIND");
159 	gnt_widget_set_name(bind_button, "bind");
160 	gnt_box_add_widget(GNT_BOX(button_box), bind_button);
161 
162 	cancel_button = gnt_button_new("Cancel");
163 	gnt_widget_set_name(cancel_button, "cancel");
164 	gnt_box_add_widget(GNT_BOX(button_box), cancel_button);
165 
166 	g_signal_connect(G_OBJECT(bind_button), "activate", G_CALLBACK(gnt_bindable_rebinding_rebind), win);
167 	g_signal_connect(G_OBJECT(cancel_button), "activate", G_CALLBACK(gnt_bindable_rebinding_cancel), win);
168 
169 	gnt_box_add_widget(GNT_BOX(vbox), button_box);
170 
171 	gnt_box_add_widget(GNT_BOX(win), vbox);
172 	gnt_widget_show(win);
173 }
174 
175 typedef struct
176 {
177 	GHashTable *hash;
178 	GntTree *tree;
179 } BindingView;
180 
181 static void
add_binding(gpointer key,gpointer value,gpointer data)182 add_binding(gpointer key, gpointer value, gpointer data)
183 {
184 	BindingView *bv = data;
185 	GntBindableActionParam *act = value;
186 	const char *name = g_hash_table_lookup(bv->hash, act->action);
187 	if (name && *name) {
188 		const char *k = gnt_key_lookup(key);
189 		if (!k)
190 			k = key;
191 		gnt_tree_add_row_after(bv->tree, (gpointer)k,
192 				gnt_tree_create_row(bv->tree, k, name), NULL, NULL);
193 	}
194 }
195 
196 static void
add_action(gpointer key,gpointer value,gpointer data)197 add_action(gpointer key, gpointer value, gpointer data)
198 {
199 	BindingView *bv = data;
200 	g_hash_table_insert(bv->hash, value, key);
201 }
202 
203 static void
gnt_bindable_class_init(GntBindableClass * klass)204 gnt_bindable_class_init(GntBindableClass *klass)
205 {
206 	parent_class = g_type_class_peek_parent(klass);
207 
208 	klass->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
209 				(GDestroyNotify)gnt_bindable_action_free);
210 	klass->bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
211 				(GDestroyNotify)gnt_bindable_action_param_free);
212 
213 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
214 	GNTDEBUG;
215 }
216 
217 static gpointer
bindable_clone(GntBindableAction * action)218 bindable_clone(GntBindableAction *action)
219 {
220 	GntBindableAction *ret = g_new0(GntBindableAction, 1);
221 	ret->name = g_strdup(action->name);
222 	ret->u = action->u;
223 	return ret;
224 }
225 
226 static gpointer
binding_clone(GntBindableActionParam * param)227 binding_clone(GntBindableActionParam *param)
228 {
229 	GntBindableActionParam *p = g_new0(GntBindableActionParam, 1);
230 	p->list = g_list_copy(param->list);
231 	p->action = param->action;
232 	return p;
233 }
234 
235 static void
duplicate_hashes(GntBindableClass * klass)236 duplicate_hashes(GntBindableClass *klass)
237 {
238 	/* Duplicate the bindings from parent class */
239 	if (klass->actions) {
240 		klass->actions = g_hash_table_duplicate(klass->actions, g_str_hash,
241 					g_str_equal, g_free, (GDestroyNotify)gnt_bindable_action_free,
242 					(GDupFunc)g_strdup, (GDupFunc)bindable_clone);
243 		klass->bindings = g_hash_table_duplicate(klass->bindings, g_str_hash,
244 					g_str_equal, g_free, (GDestroyNotify)gnt_bindable_action_param_free,
245 					(GDupFunc)g_strdup, (GDupFunc)binding_clone);
246 	} else {
247 		klass->actions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
248 					(GDestroyNotify)gnt_bindable_action_free);
249 		klass->bindings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
250 					(GDestroyNotify)gnt_bindable_action_param_free);
251 	}
252 
253 	GNTDEBUG;
254 }
255 
256 /******************************************************************************
257  * GntBindable API
258  *****************************************************************************/
259 GType
gnt_bindable_get_gtype(void)260 gnt_bindable_get_gtype(void)
261 {
262 	static GType type = 0;
263 
264 	if (type == 0) {
265 		static const GTypeInfo info = {
266 			sizeof(GntBindableClass),
267 			(GBaseInitFunc)duplicate_hashes,	/* base_init		*/
268 			NULL,					/* base_finalize	*/
269 			(GClassInitFunc)gnt_bindable_class_init,
270 			NULL,
271 			NULL,					/* class_data		*/
272 			sizeof(GntBindable),
273 			0,						/* n_preallocs		*/
274 			NULL,					/* instance_init	*/
275 			NULL					/* value_table		*/
276 		};
277 
278 		type = g_type_register_static(G_TYPE_OBJECT,
279 									  "GntBindable",
280 									  &info, G_TYPE_FLAG_ABSTRACT);
281 	}
282 
283 	return type;
284 }
285 
286 /*
287  * Key Remaps
288  */
289 const char *
gnt_bindable_remap_keys(GntBindable * bindable,const char * text)290 gnt_bindable_remap_keys(GntBindable *bindable, const char *text)
291 {
292 	const char *remap = NULL;
293 	GType type = G_OBJECT_TYPE(bindable);
294 	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bindable));
295 
296 	if (klass->remaps == NULL)
297 	{
298 		klass->remaps = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
299 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
300 		gnt_styles_get_keyremaps(type, klass->remaps);
301 G_GNUC_END_IGNORE_DEPRECATIONS
302 	}
303 
304 	remap = g_hash_table_lookup(klass->remaps, text);
305 
306 	return (remap ? remap : text);
307 }
308 
309 /*
310  * Actions and Bindings
311  */
312 gboolean
gnt_bindable_perform_action_named(GntBindable * bindable,const char * name,...)313 gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...)
314 {
315 	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bindable));
316 	GList *list = NULL;
317 	va_list args;
318 	GntBindableAction *action;
319 	void *p;
320 
321 	va_start(args, name);
322 	while ((p = va_arg(args, void *)) != NULL)
323 		list = g_list_append(list, p);
324 	va_end(args);
325 
326 	action = g_hash_table_lookup(klass->actions, name);
327 	if (action && action->u.action) {
328 		return action->u.action(bindable, list);
329 	}
330 	return FALSE;
331 }
332 
333 gboolean
gnt_bindable_perform_action_key(GntBindable * bindable,const char * keys)334 gnt_bindable_perform_action_key(GntBindable *bindable, const char *keys)
335 {
336 	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bindable));
337 	GntBindableActionParam *param = g_hash_table_lookup(klass->bindings, keys);
338 
339 	if (param && param->action) {
340 		if (param->list)
341 			return param->action->u.action(bindable, param->list);
342 		else
343 			return param->action->u.action_noparam(bindable);
344 	}
345 	return FALSE;
346 }
347 
348 gboolean
gnt_bindable_check_key(GntBindable * bindable,const char * keys)349 gnt_bindable_check_key(GntBindable *bindable, const char *keys) {
350 	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bindable));
351 	GntBindableActionParam *param = g_hash_table_lookup(klass->bindings, keys);
352 	return (param && param->action);
353 }
354 
355 static void
register_binding(GntBindableClass * klass,const char * name,const char * trigger,GList * list)356 register_binding(GntBindableClass *klass, const char *name, const char *trigger, GList *list)
357 {
358 	GntBindableActionParam *param;
359 	GntBindableAction *action;
360 
361 	if (name == NULL || *name == '\0') {
362 		g_hash_table_remove(klass->bindings, (char*)trigger);
363 		gnt_keys_del_combination(trigger);
364 		return;
365 	}
366 
367 	action = g_hash_table_lookup(klass->actions, name);
368 	if (!action) {
369 		gnt_warning("Invalid action name %s for %s",
370 				name, g_type_name(G_OBJECT_CLASS_TYPE(klass)));
371 		if (list)
372 			g_list_free(list);
373 		return;
374 	}
375 
376 	param = g_new0(GntBindableActionParam, 1);
377 	param->action = action;
378 	param->list = list;
379 	g_hash_table_replace(klass->bindings, g_strdup(trigger), param);
380 	gnt_keys_add_combination(trigger);
381 }
382 
gnt_bindable_register_binding(GntBindableClass * klass,const char * name,const char * trigger,...)383 void gnt_bindable_register_binding(GntBindableClass *klass, const char *name,
384 			const char *trigger, ...)
385 {
386 	GList *list = NULL;
387 	va_list args;
388 	void *data;
389 
390 	va_start(args, trigger);
391 	while ((data = va_arg(args, void *))) {
392 		list = g_list_append(list, data);
393 	}
394 	va_end(args);
395 
396 	register_binding(klass, name, trigger, list);
397 }
398 
gnt_bindable_class_register_action(GntBindableClass * klass,const char * name,GntBindableActionCallback callback,const char * trigger,...)399 void gnt_bindable_class_register_action(GntBindableClass *klass, const char *name,
400 			GntBindableActionCallback callback, const char *trigger, ...)
401 {
402 	void *data;
403 	va_list args;
404 	GntBindableAction *action = g_new0(GntBindableAction, 1);
405 	GList *list;
406 
407 	action->name = g_strdup(name);
408 	action->u.action = callback;
409 
410 	g_hash_table_replace(klass->actions, g_strdup(name), action);
411 
412 	if (trigger && *trigger) {
413 		list = NULL;
414 		va_start(args, trigger);
415 		while ((data = va_arg(args, void *))) {
416 			list = g_list_append(list, data);
417 		}
418 		va_end(args);
419 
420 		register_binding(klass, name, trigger, list);
421 	}
422 }
423 
gnt_bindable_action_free(GntBindableAction * action)424 void gnt_bindable_action_free(GntBindableAction *action)
425 {
426 	g_free(action->name);
427 	g_free(action);
428 }
429 
gnt_bindable_action_param_free(GntBindableActionParam * param)430 void gnt_bindable_action_param_free(GntBindableActionParam *param)
431 {
432 	g_list_free(param->list);   /* XXX: There may be a leak here for string parameters */
433 	g_free(param);
434 }
435 
gnt_bindable_bindings_view(GntBindable * bind)436 GntBindable * gnt_bindable_bindings_view(GntBindable *bind)
437 {
438 	GntBindable *tree = GNT_BINDABLE(gnt_tree_new_with_columns(2));
439 	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bind));
440 	GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal);
441 	BindingView bv = {hash, GNT_TREE(tree)};
442 
443 	gnt_tree_set_compare_func(bv.tree, (GCompareFunc)g_utf8_collate);
444 	g_hash_table_foreach(klass->actions, add_action, &bv);
445 	g_hash_table_foreach(klass->bindings, add_binding, &bv);
446 	if (GNT_TREE(tree)->list == NULL) {
447 		gnt_widget_destroy(GNT_WIDGET(tree));
448 		tree = NULL;
449 	} else
450 		gnt_tree_adjust_columns(bv.tree);
451 	g_hash_table_destroy(hash);
452 
453 	return tree;
454 }
455 
456 static void
reset_binding_window(GntBindableClass * window,gpointer k)457 reset_binding_window(GntBindableClass *window, gpointer k)
458 {
459 	GntBindableClass *klass = GNT_BINDABLE_CLASS(k);
460 	klass->help_window = NULL;
461 }
462 
463 gboolean
gnt_bindable_build_help_window(GntBindable * bindable)464 gnt_bindable_build_help_window(GntBindable *bindable)
465 {
466 	GntWidget *tree;
467 	GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
468 	char *title;
469 
470 	tree = GNT_WIDGET(gnt_bindable_bindings_view(bindable));
471 
472 	klass->help_window = GNT_BINDABLE(gnt_window_new());
473 	title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(bindable)));
474 	gnt_box_set_title(GNT_BOX(klass->help_window), title);
475 	if (tree) {
476 		g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(gnt_bindable_rebinding_activate), bindable);
477 		gnt_box_add_widget(GNT_BOX(klass->help_window), tree);
478 	} else
479 		gnt_box_add_widget(GNT_BOX(klass->help_window), gnt_label_new("This widget has no customizable bindings."));
480 
481 	g_signal_connect(G_OBJECT(klass->help_window), "destroy", G_CALLBACK(reset_binding_window), klass);
482 	gnt_widget_show(GNT_WIDGET(klass->help_window));
483 	g_free(title);
484 
485 	return TRUE;
486 }
487 
488