load menu plugins automatically from whats in the menu file
[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(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 static void parse_menu(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
68                        gpointer data)
69 {
70     ObMenuParseState *state = data;
71     gchar *name = NULL, *title = NULL, *plugin = NULL;
72
73     if (!parse_attr_string("id", node, &name))
74         goto parse_menu_fail;
75
76     if (!g_hash_table_lookup(menu_hash, name)) {
77         if (parse_attr_string("plugin", node, &plugin)) {
78             if (!plugin_open(plugin, i))
79                 goto parse_menu_fail;
80             plugin_start(plugin);
81             if (!g_hash_table_lookup(menu_hash, name))
82                 g_warning("Specified plugin '%s' did not provide the "
83                           "menu '%s'", plugin, name);
84                 goto parse_menu_fail;
85         } else {
86             if (!parse_attr_string("label", node, &title))
87                 goto parse_menu_fail;
88
89             if (menu_new(name, title, NULL)) {
90                 state->menus = g_slist_prepend(state->menus, name);
91                 parse_tree(i, doc, node->xmlChildrenNode);
92                 state->menus = g_slist_delete_link(state->menus, state->menus);
93             }
94         }
95     }
96
97     if (state->menus)
98         menu_add_submenu(state->menus->data, 0, name);
99
100 parse_menu_fail:
101     g_free(name);
102     g_free(title);
103     g_free(plugin);
104 }
105
106
107 void menu_destroy_hash_value(ObMenu *self)
108 {
109     /* XXX make sure its not visible */
110     menu_clear_entries_internal(self);
111     g_free(self->name);
112     g_free(self->title);
113 }
114
115 void menu_startup(ObParseInst *i)
116 {
117     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
118                                       (GDestroyNotify)menu_destroy_hash_value);
119 }
120
121 void menu_shutdown()
122 {
123     menu_frame_hide_all();
124     g_hash_table_destroy(menu_hash);
125     menu_hash = NULL;
126 }
127
128 void menu_parse()
129 {
130     ObParseInst *i;
131     ObMenuParseState parse_state;
132     xmlDocPtr doc;
133     xmlNodePtr node;
134     gchar *p;
135     gboolean loaded = FALSE;
136
137     i = parse_startup();
138
139     if (config_menu_path)
140         if (!(loaded =
141               parse_load(config_menu_path, "openbox_menu", &doc, &node)))
142             g_warning("Failed to load menu from '%s'", config_menu_path);
143     if (!loaded) {
144         p = g_build_filename(g_get_home_dir(), ".openbox", "menu", NULL);
145         if (!(loaded =
146               parse_load(p, "openbox_menu", &doc, &node)))
147             g_warning("Failed to load menu from '%s'", p);
148         g_free(p);
149     }
150     if (!loaded) {
151         p = g_build_filename(RCDIR, "menu", NULL);
152         if (!(loaded =
153               parse_load(p, "openbox_menu", &doc, &node)))
154             g_warning("Failed to load menu from '%s'", p);
155         g_free(p);
156     }
157
158     if (loaded) {
159         parse_state.menus = NULL;
160
161         parse_register(i, "menu", parse_menu, &parse_state);
162         parse_register(i, "item", parse_menu_item, &parse_state);
163         parse_register(i, "separator", parse_menu_separator, &parse_state);
164         parse_tree(i, doc, node->xmlChildrenNode);
165     }
166
167     parse_shutdown(i);
168 }
169
170 gboolean menu_new(gchar *name, gchar *title, gpointer data)
171 {
172     ObMenu *self;
173
174     if (g_hash_table_lookup(menu_hash, name)) return FALSE;
175
176     self = g_new0(ObMenu, 1);
177     self->name = g_strdup(name);
178     self->title = g_strdup(title);
179     self->data = data;
180
181     g_hash_table_insert(menu_hash, self->name, self);
182
183     return TRUE;
184 }
185
186 void menu_free(gchar *name)
187 {
188     ObMenu *self;
189     
190     if (!(self = menu_from_name(name))) return;
191     g_hash_table_remove(menu_hash, self->name);
192 }
193
194 void menu_show(gchar *name, gint x, gint y, ObClient *client)
195 {
196     ObMenu *self;
197     ObMenuFrame *frame;
198
199     if (!(self = menu_from_name(name))) return;
200
201     frame = menu_frame_new(self, client);
202     menu_frame_move(frame, x, y);
203     menu_frame_show(frame, NULL);
204 }
205
206 static ObMenuEntry* menu_entry_new(ObMenu *menu, ObMenuEntryType type, gint id)
207 {
208     ObMenuEntry *self;
209
210     g_assert(menu);
211
212     self = g_new0(ObMenuEntry, 1);
213     self->type = type;
214     self->menu = menu;
215     self->id = id;
216
217     switch (type) {
218     case OB_MENU_ENTRY_TYPE_NORMAL:
219         self->data.normal.enabled = TRUE;
220         break;
221     case OB_MENU_ENTRY_TYPE_SUBMENU:
222     case OB_MENU_ENTRY_TYPE_SEPARATOR:
223         break;
224     }
225
226     return self;
227 }
228
229 static void menu_entry_free(ObMenuEntry *self)
230 {
231     if (self) {
232         switch (self->type) {
233         case OB_MENU_ENTRY_TYPE_NORMAL:
234             g_free(self->data.normal.label);
235             while (self->data.normal.actions) {
236                 action_free(self->data.normal.actions->data);
237                 self->data.normal.actions =
238                     g_slist_delete_link(self->data.normal.actions,
239                                         self->data.normal.actions);
240             }
241             break;
242         case OB_MENU_ENTRY_TYPE_SUBMENU:
243         case OB_MENU_ENTRY_TYPE_SEPARATOR:
244             break;
245         }
246
247         g_free(self);
248     }
249 }
250
251 void menu_clear_entries(gchar *name)
252 {
253     ObMenu *self;
254
255     if (!(self = menu_from_name(name))) return;
256
257     menu_clear_entries_internal(self);
258 }
259
260 static void menu_clear_entries_internal(ObMenu *self)
261 {
262     /* XXX assert that the menu isn't visible */
263
264     while (self->entries) {
265         menu_entry_free(self->entries->data);
266         self->entries = g_list_delete_link(self->entries, self->entries);
267     }
268 }
269
270 void menu_add_normal(gchar *name, gint id, gchar *label, GSList *actions)
271 {
272     ObMenu *self;
273     ObMenuEntry *e;
274
275     if (!(self = menu_from_name(name))) return;
276
277     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL, id);
278     e->data.normal.label = g_strdup(label);
279     e->data.normal.actions = actions;
280
281     self->entries = g_list_append(self->entries, e);
282 }
283
284 void menu_add_submenu(gchar *name, gint id, gchar *submenu)
285 {
286     ObMenu *self, *sub;
287     ObMenuEntry *e;
288
289     if (!(self = menu_from_name(name))) return;
290     if (!(sub = menu_from_name(submenu))) return;
291
292     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SUBMENU, id);
293     e->data.submenu.submenu = sub;
294
295     self->entries = g_list_append(self->entries, e);
296 }
297
298 void menu_add_separator(gchar *name, gint id)
299 {
300     ObMenu *self;
301     ObMenuEntry *e;
302
303     if (!(self = menu_from_name(name))) return;
304
305     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR, id);
306
307     self->entries = g_list_append(self->entries, e);
308 }
309
310 void menu_set_update_func(gchar *name, ObMenuUpdateFunc func)
311 {
312     ObMenu *self;
313
314     if (!(self = menu_from_name(name))) return;
315     self->update_func = func;
316 }
317
318 ObMenuEntry* menu_find_entry_id(ObMenu *self, gint id)
319 {
320     ObMenuEntry *ret = NULL;
321     GList *it;
322
323     for (it = self->entries; it; it = g_list_next(it)) {
324         ObMenuEntry *e = it->data;
325
326         if (e->id == id) {
327             ret = e;
328             break;
329         }
330     }
331     return ret;
332 }