half way through the changes to new menu code/design. hot shit. static menus work...
[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, 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);
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;
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("label", node, &title))
78             goto parse_menu_fail;
79
80         if (menu_new(name, title, NULL)) {
81             state->menus = g_slist_prepend(state->menus, name);
82             parse_tree(i, doc, node->xmlChildrenNode);
83             state->menus = g_slist_delete_link(state->menus, state->menus);
84         }
85     }
86
87     if (state->menus)
88         menu_add_submenu(state->menus->data, name);
89
90 parse_menu_fail:
91     g_free(name);
92     g_free(title);
93 }
94
95
96 void menu_destroy_hash_value(ObMenu *self)
97 {
98     menu_clear_entries_internal(self);
99     g_free(self->name);
100     g_free(self->title);
101 }
102
103 void menu_startup(ObParseInst *i)
104 {
105     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
106                                       (GDestroyNotify)menu_destroy_hash_value);
107 }
108
109 void menu_shutdown()
110 {
111     menu_frame_hide_all();
112     g_hash_table_destroy(menu_hash);
113 }
114
115 void menu_parse()
116 {
117     ObParseInst *i;
118     ObMenuParseState parse_state;
119     xmlDocPtr doc;
120     xmlNodePtr node;
121     gchar *p;
122     gboolean loaded = FALSE;
123
124     i = parse_startup();
125
126     if (config_menu_path)
127         if (!(loaded =
128               parse_load(config_menu_path, "openbox_menu", &doc, &node)))
129             g_warning("Failed to load menu from '%s'", config_menu_path);
130     if (!loaded) {
131         p = g_build_filename(g_get_home_dir(), ".openbox", "menu", NULL);
132         if (!(loaded =
133               parse_load(p, "openbox_menu", &doc, &node)))
134             g_warning("Failed to load menu from '%s'", p);
135         g_free(p);
136     }
137     if (!loaded) {
138         p = g_build_filename(RCDIR, "menu", NULL);
139         if (!(loaded =
140               parse_load(p, "openbox_menu", &doc, &node)))
141             g_warning("Failed to load menu from '%s'", p);
142         g_free(p);
143     }
144
145     if (loaded) {
146         parse_state.menus = NULL;
147
148         parse_register(i, "menu", parse_menu, &parse_state);
149         parse_register(i, "item", parse_menu_item, &parse_state);
150         parse_register(i, "separator", parse_menu_separator, &parse_state);
151         parse_tree(i, doc, node->xmlChildrenNode);
152     }
153
154     parse_shutdown(i);
155 }
156
157 gboolean menu_new(gchar *name, gchar *title, gpointer data)
158 {
159     ObMenu *self;
160
161     if (g_hash_table_lookup(menu_hash, name)) return FALSE;
162
163     self = g_new0(ObMenu, 1);
164     self->name = g_strdup(name);
165     self->title = g_strdup(title);
166     self->data = data;
167
168     g_hash_table_insert(menu_hash, self->name, self);
169
170     return TRUE;
171 }
172
173 void menu_show(gchar *name, gint x, gint y, ObClient *client)
174 {
175     ObMenu *self;
176     ObMenuFrame *frame;
177
178     if (!(self = menu_from_name(name))) return;
179
180     /* XXX update entries */
181
182     frame = menu_frame_new(self, client);
183     menu_frame_move(frame, x, y);
184     menu_frame_show(frame, NULL);
185 }
186
187 static ObMenuEntry* menu_entry_new(ObMenu *menu, ObMenuEntryType type)
188 {
189     ObMenuEntry *self;
190
191     g_assert(menu);
192
193     self = g_new0(ObMenuEntry, 1);
194     self->type = type;
195     self->menu = menu;
196     self->enabled = TRUE;
197     return self;
198 }
199
200 static void menu_entry_free(ObMenuEntry *self)
201 {
202     if (self) {
203         switch (self->type) {
204         case OB_MENU_ENTRY_TYPE_NORMAL:
205             g_free(self->data.normal.label);
206             while (self->data.normal.actions) {
207                 action_free(self->data.normal.actions->data);
208                 self->data.normal.actions =
209                     g_slist_delete_link(self->data.normal.actions,
210                                         self->data.normal.actions);
211             }
212             break;
213         case OB_MENU_ENTRY_TYPE_SUBMENU:
214         case OB_MENU_ENTRY_TYPE_SEPARATOR:
215             break;
216         }
217
218         g_free(self);
219     }
220 }
221
222 void menu_clear_entries(gchar *name)
223 {
224     ObMenu *self;
225
226     if (!(self = menu_from_name(name))) return;
227
228     menu_clear_entries_internal(self);
229 }
230
231 static void menu_clear_entries_internal(ObMenu *self)
232 {
233     /* XXX assert that the menu isn't visible */
234
235     while (self->entries) {
236         menu_entry_free(self->entries->data);
237         self->entries = g_list_delete_link(self->entries, self->entries);
238     }
239 }
240
241 void menu_add_normal(gchar *name, gchar *label, GSList *actions)
242 {
243     ObMenu *self;
244     ObMenuEntry *e;
245
246     if (!(self = menu_from_name(name))) return;
247
248     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_NORMAL);
249     e->data.normal.label = g_strdup(label);
250     e->data.normal.actions = actions;
251
252     self->entries = g_list_append(self->entries, e);
253 }
254
255 void menu_add_submenu(gchar *name, gchar *submenu)
256 {
257     ObMenu *self, *sub;
258     ObMenuEntry *e;
259
260     if (!(self = menu_from_name(name))) return;
261     if (!(sub = menu_from_name(submenu))) return;
262
263     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SUBMENU);
264     e->data.submenu.submenu = sub;
265
266     self->entries = g_list_append(self->entries, e);
267 }
268
269 void menu_add_separator(gchar *name)
270 {
271     ObMenu *self;
272     ObMenuEntry *e;
273
274     if (!(self = menu_from_name(name))) return;
275
276     e = menu_entry_new(self, OB_MENU_ENTRY_TYPE_SEPARATOR);
277
278     self->entries = g_list_append(self->entries, e);
279 }