]> icculus.org git repositories - dana/openbox.git/blob - openbox/menu.c
Menu parsing updates for plugins.
[dana/openbox.git] / openbox / menu.c
1 #include "menu.h"
2 #include "openbox.h"
3 #include "stacking.h"
4 #include "client.h"
5 #include "grab.h"
6 #include "screen.h"
7 #include "geom.h"
8 #include "plugin.h"
9
10 GHashTable *menu_hash = NULL;
11 GSList *menu_visible = NULL;
12
13 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask | \
14                          LeaveWindowMask)
15 #define TITLE_EVENTMASK (ButtonPressMask | ButtonMotionMask)
16 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
17                          ButtonPressMask | ButtonReleaseMask)
18
19 static void parse_menu(xmlDocPtr doc, xmlNodePtr node, void *data)
20 {
21     parse_menu_full(doc, node, data, TRUE);
22 }
23
24
25 void parse_menu_full(xmlDocPtr doc, xmlNodePtr node, void *data,
26                        gboolean newmenu)
27 {
28     Action *act;
29     xmlNodePtr nact;
30
31     gchar *id = NULL, *title = NULL, *label = NULL, *plugin;
32     ObMenu *menu = NULL, *parent;
33
34     if (newmenu == TRUE) {
35         if (!parse_attr_string("id", node->parent, &id))
36             goto parse_menu_fail;
37         if (!parse_attr_string("label", node->parent, &title))
38             goto parse_menu_fail;
39
40         g_message("menu label %s", title);
41
42         menu = menu_new(title, id, data ? *((ObMenu**)data) : NULL);
43
44         if (data)
45             *((ObMenu**)data) = menu;
46     } else {
47         menu = (ObMenu *)data;
48     }
49     
50     while (node) {
51         if (!xmlStrcasecmp(node->name, (const xmlChar*) "menu")) {
52             if (parse_attr_string("plugin", node, &plugin)) {
53                 PluginMenuCreateData data = {
54                     .doc = doc,
55                     .node = node,
56                     .parent = menu
57                 };
58                 parent = plugin_create(plugin, &data);
59             } else {
60                 parent = menu;
61                 parse_menu(doc, node->xmlChildrenNode, &parent);
62                 menu_add_entry(menu, menu_entry_new_submenu(parent->label,
63                                                             parent));
64             }
65
66         }
67         else if (!xmlStrcasecmp(node->name, (const xmlChar*) "item")) {
68             if (parse_attr_string("label", node, &label)) {
69                 if ((nact = parse_find_node("action", node->xmlChildrenNode)))
70                     act = action_parse(doc, nact);
71                 else
72                     act = NULL;
73                 if (act)
74                     menu_add_entry(menu, menu_entry_new(label, act));
75                 else
76                     menu_add_entry(menu, menu_entry_new_separator(label));
77                 g_free(label);
78             }
79         }
80         node = node->next;
81     }
82
83 parse_menu_fail:
84     g_free(id);
85     g_free(title);
86 }
87
88 void menu_control_show(ObMenu *self, int x, int y, ObClient *client);
89
90 void menu_destroy_hash_key(ObMenu *menu)
91 {
92     g_free(menu);
93 }
94
95 void menu_destroy_hash_value(ObMenu *self)
96 {
97     GList *it;
98
99     for (it = self->entries; it; it = it->next)
100         menu_entry_free(it->data);
101     g_list_free(self->entries);
102
103     g_free(self->label);
104     g_free(self->name);
105
106     g_hash_table_remove(window_map, &self->title);
107     g_hash_table_remove(window_map, &self->frame);
108     g_hash_table_remove(window_map, &self->items);
109
110     stacking_remove(self);
111
112     RrAppearanceFree(self->a_title);
113     XDestroyWindow(ob_display, self->title);
114     XDestroyWindow(ob_display, self->frame);
115     XDestroyWindow(ob_display, self->items);
116
117     g_free(self);
118 }
119
120 void menu_entry_free(ObMenuEntry *self)
121 {
122     g_free(self->label);
123     action_free(self->action);
124
125     g_hash_table_remove(window_map, &self->item);
126
127     RrAppearanceFree(self->a_item);
128     RrAppearanceFree(self->a_disabled);
129     RrAppearanceFree(self->a_hilite);
130     XDestroyWindow(ob_display, self->item);
131
132     g_free(self);
133 }
134     
135 void menu_startup()
136 {
137 /*
138     ObMenu *m;
139     ObMenu *s;
140     ObMenu *t;
141     Action *a;
142 */
143
144     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
145                                       (GDestroyNotify)menu_destroy_hash_key,
146                                       (GDestroyNotify)menu_destroy_hash_value);
147
148     parse_register("menu", parse_menu, NULL);
149
150 /*
151     m = menu_new("sex menu", "root", NULL);
152  
153     a = action_from_string("execute");
154     a->data.execute.path = g_strdup("xterm");
155     menu_add_entry(m, menu_entry_new("xterm", a));
156     a = action_from_string("restart");
157     menu_add_entry(m, menu_entry_new("restart", a));
158     menu_add_entry(m, menu_entry_new_separator("--"));
159     a = action_from_string("exit");
160     menu_add_entry(m, menu_entry_new("exit", a));
161 */
162
163     /*
164     s = menu_new("subsex menu", "submenu", m);
165     a = action_from_string("execute");
166     a->data.execute.path = g_strdup("xclock");
167     menu_add_entry(s, menu_entry_new("xclock", a));
168
169     menu_add_entry(m, menu_entry_new_submenu("subz", s));
170
171     s = menu_new("empty", "chub", m);
172     menu_add_entry(m, menu_entry_new_submenu("empty", s));
173
174     s = menu_new("", "s-club", m);
175     menu_add_entry(m, menu_entry_new_submenu("empty", s));
176
177     s = menu_new(NULL, "h-club", m);
178     menu_add_entry(m, menu_entry_new_submenu("empty", s));
179
180     s = menu_new(NULL, "g-club", m);
181
182     a = action_from_string("execute");
183     a->data.execute.path = g_strdup("xterm");
184     menu_add_entry(s, menu_entry_new("xterm", a));
185     a = action_from_string("restart");
186     menu_add_entry(s, menu_entry_new("restart", a));
187     menu_add_entry(s, menu_entry_new_separator("--"));
188     a = action_from_string("exit");
189     menu_add_entry(s, menu_entry_new("exit", a));
190
191     menu_add_entry(m, menu_entry_new_submenu("long", s));
192     */
193 }
194
195 void menu_shutdown()
196 {
197     g_hash_table_destroy(menu_hash);
198 }
199
200 static Window createWindow(Window parent, unsigned long mask,
201                            XSetWindowAttributes *attrib)
202 {
203     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
204                          RrDepth(ob_rr_inst), InputOutput,
205                          RrVisual(ob_rr_inst), mask, attrib);
206                        
207 }
208
209 ObMenu *menu_new_full(char *label, char *name, ObMenu *parent, 
210                     menu_controller_show show, menu_controller_update update)
211 {
212     XSetWindowAttributes attrib;
213     ObMenu *self;
214
215     self = g_new0(ObMenu, 1);
216     self->obwin.type = Window_Menu;
217     self->label = g_strdup(label);
218     self->name = g_strdup(name);
219     self->parent = parent;
220     self->open_submenu = NULL;
221
222     self->entries = NULL;
223     self->shown = FALSE;
224     self->invalid = TRUE;
225
226     /* default controllers */
227     self->show = show;
228     self->hide = NULL;
229     self->update = update;
230     self->mouseover = NULL;
231     self->selected = NULL;
232
233     self->plugin = NULL;
234     self->plugin_data = NULL;
235
236     attrib.override_redirect = TRUE;
237     attrib.event_mask = FRAME_EVENTMASK;
238     self->frame = createWindow(RootWindow(ob_display, ob_screen),
239                                CWOverrideRedirect|CWEventMask, &attrib);
240     attrib.event_mask = TITLE_EVENTMASK;
241     self->title = createWindow(self->frame, CWEventMask, &attrib);
242     self->items = createWindow(self->frame, 0, &attrib);
243
244     self->a_title = self->a_items = NULL;
245
246     XMapWindow(ob_display, self->title);
247     XMapWindow(ob_display, self->items);
248
249     g_hash_table_insert(window_map, &self->frame, self);
250     g_hash_table_insert(window_map, &self->title, self);
251     g_hash_table_insert(window_map, &self->items, self);
252     g_hash_table_insert(menu_hash, g_strdup(name), self);
253
254     stacking_add(MENU_AS_WINDOW(self));
255     stacking_raise(MENU_AS_WINDOW(self));
256
257     return self;
258 }
259
260 void menu_free(char *name)
261 {
262     g_hash_table_remove(menu_hash, name);
263 }
264
265 ObMenuEntry *menu_entry_new_full(char *label, Action *action,
266                                ObMenuEntryRenderType render_type,
267                                gpointer submenu)
268 {
269     ObMenuEntry *menu_entry = g_new0(ObMenuEntry, 1);
270
271     menu_entry->label = g_strdup(label);
272     menu_entry->render_type = render_type;
273     menu_entry->action = action;
274
275     menu_entry->hilite = FALSE;
276     menu_entry->enabled = TRUE;
277
278     menu_entry->submenu = submenu;
279
280     return menu_entry;
281 }
282
283 void menu_entry_set_submenu(ObMenuEntry *entry, ObMenu *submenu)
284 {
285     g_assert(entry != NULL);
286     
287     entry->submenu = submenu;
288
289     if(entry->parent != NULL)
290         entry->parent->invalid = TRUE;
291 }
292
293 void menu_add_entry(ObMenu *menu, ObMenuEntry *entry)
294 {
295     XSetWindowAttributes attrib;
296
297     g_assert(menu != NULL);
298     g_assert(entry != NULL);
299     g_assert(entry->item == None);
300
301     menu->entries = g_list_append(menu->entries, entry);
302     entry->parent = menu;
303
304     attrib.event_mask = ENTRY_EVENTMASK;
305     entry->item = createWindow(menu->items, CWEventMask, &attrib);
306     XMapWindow(ob_display, entry->item);
307
308     entry->a_item = entry->a_disabled = entry->a_hilite = NULL;
309
310     menu->invalid = TRUE;
311
312     g_hash_table_insert(window_map, &entry->item, menu);
313 }
314
315 void menu_show(char *name, int x, int y, ObClient *client)
316 {
317     ObMenu *self;
318   
319     self = g_hash_table_lookup(menu_hash, name);
320     if (!self) {
321         g_warning("Attempted to show menu '%s' but it does not exist.",
322                   name);
323         return;
324     }
325
326     menu_show_full(self, x, y, client);
327 }  
328
329 void menu_show_full(ObMenu *self, int x, int y, ObClient *client)
330 {
331     g_assert(self != NULL);
332        
333     menu_render(self);
334     
335     self->client = client;
336
337     if (!self->shown) {
338         if (!self->parent) {
339             grab_pointer(TRUE, None);
340             grab_keyboard(TRUE);
341         }
342         menu_visible = g_slist_append(menu_visible, self);
343     }
344
345     if (self->show) {
346         self->show(self, x, y, client);
347     } else {
348       menu_control_show(self, x, y, client);
349     }
350 }
351
352 void menu_hide(ObMenu *self) {
353     if (self->shown) {
354         XUnmapWindow(ob_display, self->frame);
355         self->shown = FALSE;
356         if (self->open_submenu)
357             menu_hide(self->open_submenu);
358         if (self->parent && self->parent->open_submenu == self)
359             self->parent->open_submenu = NULL;
360
361         if (!self->parent) {
362             grab_keyboard(FALSE);
363             grab_pointer(FALSE, None);
364         }
365         menu_visible = g_slist_remove(menu_visible, self);
366     }
367 }
368
369 void menu_clear(ObMenu *self) {
370     GList *it;
371   
372     for (it = self->entries; it; it = it->next) {
373         ObMenuEntry *entry = it->data;
374         menu_entry_free(entry);
375     }
376     self->entries = NULL;
377     self->invalid = TRUE;
378 }
379
380
381 ObMenuEntry *menu_find_entry(ObMenu *menu, Window win)
382 {
383     GList *it;
384
385     for (it = menu->entries; it; it = it->next) {
386         ObMenuEntry *entry = it->data;
387         if (entry->item == win)
388             return entry;
389     }
390     return NULL;
391 }
392
393 ObMenuEntry *menu_find_entry_by_pos(ObMenu *menu, int x, int y)
394 {
395     if (x < 0 || x >= menu->size.width || y < 0 || y >= menu->size.height)
396         return NULL;
397
398     y -= menu->title_h + ob_rr_theme->bwidth;
399     if (y < 0) return NULL;
400     
401     g_message ("%d %p", y/menu->item_h, g_list_nth_data(menu->entries, y / menu->item_h));
402     return g_list_nth_data(menu->entries, y / menu->item_h);
403 }
404
405 void menu_entry_fire(ObMenuEntry *self)
406 {
407     ObMenu *m;
408
409     if (self->action) {
410         self->action->data.any.c = self->parent->client;
411         self->action->func(&self->action->data);
412
413         /* hide the whole thing */
414         m = self->parent;
415         while (m->parent) m = m->parent;
416         menu_hide(m);
417     }
418 }
419
420 /* 
421    Default menu controller action for showing.
422 */
423
424 void menu_control_show(ObMenu *self, int x, int y, ObClient *client) {
425     guint i;
426     Rect *a = NULL;
427
428     g_assert(!self->invalid);
429     
430     for (i = 0; i < screen_num_monitors; ++i) {
431         a = screen_physical_area_monitor(i);
432         if (RECT_CONTAINS(*a, x, y))
433             break;
434     }
435     g_assert(a != NULL);
436     self->xin_area = i;
437
438     POINT_SET(self->location,
439               MIN(x, a->x + a->width - 1 - self->size.width), 
440               MIN(y, a->y + a->height - 1 - self->size.height));
441     XMoveWindow(ob_display, self->frame, self->location.x, self->location.y);
442
443     if (!self->shown) {
444         XMapWindow(ob_display, self->frame);
445         stacking_raise(MENU_AS_WINDOW(self));
446         self->shown = TRUE;
447     } else if (self->shown && self->open_submenu) {
448         menu_hide(self->open_submenu);
449     }
450 }
451
452 void menu_control_mouseover(ObMenuEntry *self, gboolean enter) {
453     int x;
454     Rect *a;
455
456     self->hilite = enter;
457   
458     if (enter) {
459         if (self->parent->open_submenu && self->submenu 
460             != self->parent->open_submenu)
461             menu_hide(self->parent->open_submenu);
462         
463         if (self->submenu && self->parent->open_submenu != self->submenu) {
464             self->parent->open_submenu = self->submenu;
465
466             /* shouldn't be invalid since it must be displayed */
467             g_assert(!self->parent->invalid);
468             /* TODO: I don't understand why these bevels should be here.
469                Something must be wrong in the width calculation */
470             x = self->parent->location.x + self->parent->size.width + 
471                 ob_rr_theme->bwidth;
472
473             /* need to get the width. is this bad?*/
474             menu_render(self->submenu);
475
476             a = screen_physical_area_monitor(self->parent->xin_area);
477
478             if (self->submenu->size.width + x >= a->x + a->width)
479                 x = self->parent->location.x - self->submenu->size.width - 
480                     ob_rr_theme->bwidth;
481             
482             menu_show_full(self->submenu, x,
483                            self->parent->location.y + self->y,
484                            self->parent->client);
485         } 
486     }
487 }