fix a bunch of memleaks from valgrind and stuff
[dana/openbox.git] / openbox / menu.c
1 #include "debug.h"
2 #include "menu.h"
3 #include "openbox.h"
4 #include "stacking.h"
5 #include "client.h"
6 #include "grab.h"
7 #include "config.h"
8 #include "screen.h"
9 #include "menuframe.h"
10 #include "geom.h"
11 #include "plugin.h"
12 #include "misc.h"
13 #include "parser/parse.h"
14
15 static GHashTable *menu_hash = NULL;
16
17 ObParseInst *menu_parse_inst;
18
19 typedef struct _ObMenuParseState ObMenuParseState;
20
21 struct _ObMenuParseState
22 {
23     GSList *menus;
24 };
25
26 static void menu_clear_entries_internal(ObMenu *self);
27
28 static ObMenu* menu_from_name(gchar *name)
29 {
30     ObMenu *self = NULL;
31
32     g_assert(name != NULL);
33
34     if (!(self = g_hash_table_lookup(menu_hash, name)))
35         g_warning("Attempted to access menu '%s' but it does not exist.",
36                   name);
37     return self;
38 }  
39
40 static void parse_menu_item(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
41                             gpointer data)
42 {
43     ObMenuParseState *state = data;
44     gchar *label;
45     
46     if (state->menus) {
47         if (parse_attr_string("label", node, &label)) {
48             GSList *acts = NULL;
49
50             for (node = node->xmlChildrenNode; node; node = node->next)
51                 if (!xmlStrcasecmp(node->name, (const xmlChar*) "action"))
52                     acts = g_slist_append(acts, action_parse(i, doc, node));
53             menu_add_normal(state->menus->data, 0, label, acts);
54             g_free(label);
55         }
56     }
57 }
58
59 static void parse_menu_separator(ObParseInst *i,
60                                  xmlDocPtr doc, xmlNodePtr node,
61                                  gpointer data)
62 {
63     ObMenuParseState *state = data;
64
65     if (state->menus)
66         menu_add_separator(state->menus->data, 0);
67 }
68
69 gboolean menu_open_plugin(ObParseInst *i, gchar *name, gchar *plugin)
70 {
71     gboolean ret = FALSE;
72
73     if (plugin_open(plugin, i)) {
74         plugin_start(plugin);
75         if (g_hash_table_lookup(menu_hash, name))
76             ret = TRUE;
77         else
78             g_warning("Specified plugin '%s' did not provide the "
79                       "menu '%s'", plugin, name);
80     }
81     return ret;
82 }
83
84 static void parse_menu(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
85                        gpointer data)
86 {
87     ObMenuParseState *state = data;
88     gchar *name = NULL, *title = NULL, *plugin = NULL;
89
90     if (!parse_attr_string("id", node, &name))
91         goto parse_menu_fail;
92
93     if (!g_hash_table_lookup(menu_hash, name)) {
94         if (parse_attr_string("plugin", node, &plugin)) {
95             menu_open_plugin(i, name, plugin);
96         } else {
97             if (!parse_attr_string("label", node, &title))
98                 goto parse_menu_fail;
99
100             if (menu_new(name, title, NULL)) {
101                 state->menus = g_slist_prepend(state->menus, name);
102                 parse_tree(i, doc, node->xmlChildrenNode);
103                 state->menus = g_slist_delete_link(state->menus, state->menus);
104             }
105         }
106     }
107
108     if (state->menus)
109         menu_add_submenu(state->menus->data, 0, name);
110
111 parse_menu_fail:
112     g_free(name);
113     g_free(title);
114     g_free(plugin);
115 }
116
117
118 void menu_destroy_hash_value(ObMenu *self)
119 {
120     /* XXX make sure its not visible */
121
122     if (self->destroy_func)
123         self->destroy_func(self, self->data);
124
125     menu_clear_entries_internal(self);
126     g_free(self->name);
127     g_free(self->title);
128 }
129
130 void menu_startup(ObParseInst *i)
131 {
132     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
133                                       (GDestroyNotify)menu_destroy_hash_value);
134     menu_parse_inst = parse_startup();
135 }
136
137 void menu_shutdown()
138 {
139     parse_shutdown(menu_parse_inst);
140     menu_parse_inst = NULL;
141
142     menu_frame_hide_all();
143     g_hash_table_destroy(menu_hash);
144     menu_hash = NULL;
145 }
146
147 gboolean menu_open(gchar *file, xmlDocPtr *doc, xmlNodePtr *node)
148 {
149     gboolean loaded = TRUE;
150     gchar *p;
151
152     p = g_build_filename(g_get_home_dir(), ".openbox", file, NULL);
153     if (!parse_load(p, "openbox_menu", doc, node)) {
154         g_free(p);
155         p = g_build_filename(RCDIR, file, NULL);
156         if (!parse_load(p, "openbox_menu", doc, node)) {
157             g_free(p);
158             p = g_strdup(file);
159             if (!parse_load(p, "openbox_menu", doc, node)) {
160                 g_warning("Failed to load menu from '%s'", file);
161                 loaded = FALSE;
162             }
163         }
164     }
165     g_free(p);
166     return loaded;
167 }
168
169 void menu_parse()
170 {
171     ObMenuParseState parse_state;
172     xmlDocPtr doc;
173     xmlNodePtr node;
174     gboolean loaded = FALSE;
175     GSList *it;
176
177     for (it = config_menu_files; it; it = g_slist_next(it)) {
178         if (menu_open(it->data, &doc, &node))
179             loaded = TRUE;
180
181     }
182     if (!loaded)
183         loaded = menu_open("menu", &doc, &node);
184
185     if (loaded) {
186         parse_state.menus = NULL;
187
188         parse_register(menu_parse_inst, "menu", parse_menu, &parse_state);
189         parse_register(menu_parse_inst, "item", parse_menu_item, &parse_state);
190         parse_register(menu_parse_inst, "separator",
191                        parse_menu_separator, &parse_state);
192         parse_tree(menu_parse_inst, doc, node->xmlChildrenNode);
193         xmlFreeDoc(doc);
194     }
195 }
196
197 ObMenu* menu_new(gchar *name, gchar *title, gpointer data)
198 {
199     ObMenu *self;
200
201     /*if (g_hash_table_lookup(menu_hash, name)) return FALSE;*/
202
203     self = g_new0(ObMenu, 1);
204     self->name = g_strdup(name);
205     self->title = g_strdup(title);
206     self->data = data;
207
208     g_hash_table_replace(menu_hash, self->name, self);
209
210     return self;
211 }
212
213 void menu_free(gchar *name)
214 {
215     ObMenu *self;
216     
217     if (!(self = menu_from_name(name))) return;
218     g_hash_table_remove(menu_hash, self->name);
219 }
220
221 void menu_show(gchar *name, gint x, gint y, ObClient *client)
222 {
223     ObMenu *self;
224     ObMenuFrame *frame;
225
226     if (!(self = menu_from_name(name))) return;
227
228     frame = menu_frame_new(self, client);
229     menu_frame_move(frame, x, y);
230     menu_frame_show(frame, NULL);
231 }
232
233 static ObMenuEntry* menu_entry_new(ObMenu *menu, ObMenuEntryType type, gint id)
234 {
235     ObMenuEntry *self;
236
237     g_assert(menu);
238
239     self = g_new0(ObMenuEntry, 1);
240     self->type = type;
241     self->menu = menu;
242     self->id = id;
243
244     switch (type) {
245     case OB_MENU_ENTRY_TYPE_NORMAL:
246         self->data.normal.enabled = TRUE;
247         break;
248     case OB_MENU_ENTRY_TYPE_SUBMENU:
249     case OB_MENU_ENTRY_TYPE_SEPARATOR:
250         break;
251     }
252
253     return self;
254 }
255
256 static void menu_entry_free(ObMenuEntry *self)
257 {
258     if (self) {
259         switch (self->type) {
260         case OB_MENU_ENTRY_TYPE_NORMAL:
261             g_free(self->data.normal.label);
262             while (self->data.normal.actions) {
263                 action_free(self->data.normal.actions->data);
264                 self->data.normal.actions =
265                     g_slist_delete_link(self->data.normal.actions,
266                                         self->data.normal.actions);
267             }
268             break;
269         case OB_MENU_ENTRY_TYPE_SUBMENU:
270             g_free(self->data.submenu.name);
271             break;
272         case OB_MENU_ENTRY_TYPE_SEPARATOR:
273             break;
274         }
275
276         g_free(self);
277     }
278 }
279
280 void menu_clear_entries(gchar *name)
281 {
282     ObMenu *self;
283
284     if (!(self = menu_from_name(name))) return;
285
286     menu_clear_entries_internal(self);
287 }
288
289 static void menu_clear_entries_internal(ObMenu *self)
290 {
291     /* XXX assert that the menu isn't visible */
292
293     while (self->entries) {
294         menu_entry_free(self->entries->data);
295         self->entries = g_list_delete_link(self->entries, self->entries);
296     }
297 }
298
299 ObMenuEntry* menu_add_normal(gchar *name, gint id, gchar *label,
300                              GSList *actions)
301 {
302     ObMenu *self;
303     ObMenuEntry *e;
304
305     if (!(self = menu_from_name(name))) return;
306
307     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL, id);
308     e->data.normal.label = g_strdup(label);
309     e->data.normal.actions = actions;
310
311     self->entries = g_list_append(self->entries, e);
312     return e;
313 }
314
315 ObMenuEntry* menu_add_submenu(gchar *name, gint id, gchar *submenu)
316 {
317     ObMenu *self;
318     ObMenuEntry *e;
319
320     if (!(self = menu_from_name(name))) return;
321
322     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SUBMENU, id);
323     e->data.submenu.name = g_strdup(submenu);
324
325     self->entries = g_list_append(self->entries, e);
326     return e;
327 }
328
329 ObMenuEntry* menu_add_separator(gchar *name, gint id)
330 {
331     ObMenu *self;
332     ObMenuEntry *e;
333
334     if (!(self = menu_from_name(name))) return;
335
336     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR, id);
337
338     self->entries = g_list_append(self->entries, e);
339     return e;
340 }
341
342 void menu_set_update_func(gchar *name, ObMenuUpdateFunc func)
343 {
344     ObMenu *self;
345
346     if (!(self = menu_from_name(name))) return;
347     self->update_func = func;
348 }
349
350 void menu_set_execute_func(gchar *name, ObMenuExecuteFunc func)
351 {
352     ObMenu *self;
353
354     if (!(self = menu_from_name(name))) return;
355     self->execute_func = func;
356 }
357
358 void menu_set_destroy_func(gchar *name, ObMenuDestroyFunc func)
359 {
360     ObMenu *self;
361
362     if (!(self = menu_from_name(name))) return;
363     self->destroy_func = func;
364 }
365
366 ObMenuEntry* menu_find_entry_id(ObMenu *self, gint id)
367 {
368     ObMenuEntry *ret = NULL;
369     GList *it;
370
371     for (it = self->entries; it; it = g_list_next(it)) {
372         ObMenuEntry *e = it->data;
373
374         if (e->id == id) {
375             ret = e;
376             break;
377         }
378     }
379     return ret;
380 }
381
382 void menu_find_submenus(ObMenu *self)
383 {
384     GList *it;
385
386     for (it = self->entries; it; it = g_list_next(it)) {
387         ObMenuEntry *e = it->data;
388
389         if (e->type == OB_MENU_ENTRY_TYPE_SUBMENU)
390             e->data.submenu.submenu = menu_from_name(e->data.submenu.name);
391     }
392 }