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