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