]> icculus.org git repositories - dana/openbox.git/blob - openbox/menu.c
adjust for changes to the parsing api.
[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 "geom.h"
10 #include "plugin.h"
11 #include "misc.h"
12 #include "parser/parse.h"
13
14 GHashTable *menu_hash = NULL;
15 GList *menu_visible = NULL;
16
17 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\
18                          LeaveWindowMask)
19 #define TITLE_EVENTMASK (ButtonPressMask | ButtonMotionMask)
20 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
21                          ButtonPressMask | ButtonReleaseMask)
22
23 static void parse_menu(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
24                        gpointer data)
25 {
26     g_message("%s", __FUNCTION__);
27     parse_menu_full(i, doc, node, data, TRUE);
28 }
29
30
31 void parse_menu_full(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node,
32                      gpointer data, gboolean newmenu)
33 {
34     ObAction *act;
35     xmlNodePtr nact;
36
37     gchar *id = NULL, *title = NULL, *label = NULL, *plugin;
38     ObMenu *menu = NULL, *parent;
39
40     if (newmenu == TRUE) {
41         if (!parse_attr_string("id", node, &id))
42             goto parse_menu_fail;
43         if (!parse_attr_string("label", node, &title))
44             goto parse_menu_fail;
45         ob_debug("menu label %s\n", title);
46
47         if (parse_attr_string("plugin", node, &plugin)) {
48             PluginMenuCreateData data;
49             data.parse_inst = i;
50             data.doc = doc;
51             data.node = node;
52             data.parent = menu;
53
54             if (plugin_open_reopen(plugin, i))
55                 parent = plugin_create(plugin, &data);
56             g_free(plugin);
57         } else
58             menu = menu_new(title, id, data ? *((ObMenu**)data) : NULL);
59             
60         if (data)
61             *((ObMenu**)data) = menu;
62     } else {
63         menu = (ObMenu *)data;
64     }
65
66     node = node->xmlChildrenNode;
67     
68     while (node) {
69         if (!xmlStrcasecmp(node->name, (const xmlChar*) "menu")) {
70             if (parse_attr_string("plugin", node, &plugin)) {
71                 PluginMenuCreateData data;
72                 data.doc = doc;
73                 data.node = node;
74                 data.parent = menu;
75                 if (plugin_open_reopen(plugin, i))
76                     parent = plugin_create(plugin, &data);
77                 g_free(plugin);
78             } else {
79                 parent = menu;
80                 parse_menu(i, doc, node, &parent);
81                 menu_add_entry(menu, menu_entry_new_submenu(parent->label,
82                                                             parent));
83             }
84
85         }
86         else if (!xmlStrcasecmp(node->name, (const xmlChar*) "item")) {
87             if (parse_attr_string("label", node, &label)) {
88                 if ((nact = parse_find_node("action", node->xmlChildrenNode)))
89                     act = action_parse(doc, nact);
90                 else
91                     act = NULL;
92                 if (act)
93                     menu_add_entry(menu, menu_entry_new(label, act));
94                 else
95                     menu_add_entry(menu, menu_entry_new_separator(label));
96                 g_free(label);
97             }
98         }
99         node = node->next;
100     }
101
102 parse_menu_fail:
103     g_free(id);
104     g_free(title);
105 }
106
107 void menu_control_show(ObMenu *self, int x, int y, ObClient *client);
108
109 void menu_destroy_hash_key(ObMenu *menu)
110 {
111     g_free(menu);
112 }
113
114 void menu_destroy_hash_value(ObMenu *self)
115 {
116     GList *it;
117
118     for (it = self->entries; it; it = it->next)
119         menu_entry_free(it->data);
120     g_list_free(self->entries);
121
122     g_free(self->label);
123     g_free(self->name);
124
125     g_hash_table_remove(window_map, &self->title);
126     g_hash_table_remove(window_map, &self->frame);
127     g_hash_table_remove(window_map, &self->items);
128
129     stacking_remove(self);
130
131     RrAppearanceFree(self->a_title);
132     RrAppearanceFree(self->a_items);
133     XDestroyWindow(ob_display, self->title);
134     XDestroyWindow(ob_display, self->frame);
135     XDestroyWindow(ob_display, self->items);
136
137     g_free(self);
138 }
139
140 void menu_entry_free(ObMenuEntry *self)
141 {
142     g_free(self->label);
143     action_free(self->action);
144
145     g_hash_table_remove(window_map, &self->item);
146
147     RrAppearanceFree(self->a_item);
148     RrAppearanceFree(self->a_disabled);
149     RrAppearanceFree(self->a_hilite);
150     RrAppearanceFree(self->a_submenu);
151     XDestroyWindow(ob_display, self->item);
152     XDestroyWindow(ob_display, self->submenu_pic);
153     g_free(self);
154 }
155  
156 void menu_startup(ObParseInst *i)
157 {
158     menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
159                                       (GDestroyNotify)menu_destroy_hash_key,
160                                       (GDestroyNotify)menu_destroy_hash_value);
161 }
162
163 void menu_shutdown()
164 {
165     g_hash_table_destroy(menu_hash);
166 }
167
168 void menu_parse()
169 {
170     ObParseInst *i;
171     xmlDocPtr doc;
172     xmlNodePtr node;
173     gchar *p;
174     gboolean loaded = FALSE;
175
176     i = parse_startup();
177
178     if (config_menu_path)
179         if (!(loaded =
180               parse_load(config_menu_path, "openbox_menu", &doc, &node)))
181             g_warning("Failed to load menu from '%s'", config_menu_path);
182     if (!loaded) {
183         p = g_build_filename(g_get_home_dir(), ".openbox", "menu", NULL);
184         if (!(loaded =
185               parse_load(p, "openbox_menu", &doc, &node)))
186             g_warning("Failed to load menu from '%s'", p);
187         g_free(p);
188     }
189     if (!loaded) {
190         p = g_build_filename(RCDIR, "menu", NULL);
191         if (!(loaded =
192               parse_load(p, "openbox_menu", &doc, &node)))
193             g_warning("Failed to load menu from '%s'", p);
194         g_free(p);
195     }
196
197     if (loaded) {
198         parse_register(i, "menu", parse_menu, NULL);
199         parse_tree(i, doc, node->xmlChildrenNode);
200     }
201
202     parse_shutdown(i);
203 }
204
205 static Window createWindow(Window parent, unsigned long mask,
206                            XSetWindowAttributes *attrib)
207 {
208     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
209                          RrDepth(ob_rr_inst), InputOutput,
210                          RrVisual(ob_rr_inst), mask, attrib);
211                        
212 }
213
214 ObMenu *menu_new_full(char *label, char *name, ObMenu *parent, 
215                       menu_controller_show show, menu_controller_update update,
216                       menu_controller_selected selected,
217                       menu_controller_hide hide,
218                       menu_controller_mouseover mouseover)
219 {
220     XSetWindowAttributes attrib;
221     ObMenu *self;
222
223     self = g_new0(ObMenu, 1);
224     self->obwin.type = Window_Menu;
225     self->label = g_strdup(label);
226     self->name = g_strdup(name);
227     self->parent = parent;
228     self->open_submenu = NULL;
229     self->over = NULL;
230
231     self->entries = NULL;
232     self->shown = FALSE;
233     self->invalid = TRUE;
234
235     /* default controllers */
236     self->show = (show != NULL ? show : menu_show_full);
237     self->hide = (hide != NULL ? hide : menu_hide);
238     self->update = (update != NULL ? update : menu_render);
239     self->mouseover = (mouseover != NULL ? mouseover :
240                        menu_control_mouseover);
241     self->selected = (selected != NULL ? selected : menu_entry_fire);
242
243     self->plugin = NULL;
244     self->plugin_data = NULL;
245
246     attrib.override_redirect = TRUE;
247     attrib.event_mask = FRAME_EVENTMASK;
248     self->frame = createWindow(RootWindow(ob_display, ob_screen),
249                                CWOverrideRedirect|CWEventMask, &attrib);
250     attrib.event_mask = TITLE_EVENTMASK;
251     self->title = createWindow(self->frame, CWEventMask, &attrib);
252     self->items = createWindow(self->frame, 0, &attrib);
253
254     self->a_title = self->a_items = NULL;
255
256     XMapWindow(ob_display, self->title);
257     XMapWindow(ob_display, self->items);
258
259     g_hash_table_insert(window_map, &self->frame, self);
260     g_hash_table_insert(window_map, &self->title, self);
261     g_hash_table_insert(window_map, &self->items, self);
262     g_hash_table_insert(menu_hash, g_strdup(name), self);
263
264     stacking_add(MENU_AS_WINDOW(self));
265     stacking_raise(MENU_AS_WINDOW(self));
266
267     return self;
268 }
269
270 void menu_free(char *name)
271 {
272     g_hash_table_remove(menu_hash, name);
273 }
274
275 ObMenuEntry *menu_entry_new_full(char *label, ObAction *action,
276                                ObMenuEntryRenderType render_type,
277                                gpointer submenu)
278 {
279     ObMenuEntry *menu_entry = g_new0(ObMenuEntry, 1);
280
281     menu_entry->label = g_strdup(label);
282     menu_entry->render_type = render_type;
283     menu_entry->action = action;
284
285     menu_entry->hilite = FALSE;
286     menu_entry->enabled = TRUE;
287
288     menu_entry->submenu = submenu;
289
290     return menu_entry;
291 }
292
293 void menu_entry_set_submenu(ObMenuEntry *entry, ObMenu *submenu)
294 {
295     g_assert(entry != NULL);
296     
297     entry->submenu = submenu;
298
299     if(entry->parent != NULL)
300         entry->parent->invalid = TRUE;
301 }
302
303 void menu_add_entry(ObMenu *menu, ObMenuEntry *entry)
304 {
305     XSetWindowAttributes attrib;
306
307     g_assert(menu != NULL);
308     g_assert(entry != NULL);
309     g_assert(entry->item == None);
310
311     menu->entries = g_list_append(menu->entries, entry);
312     entry->parent = menu;
313
314     attrib.event_mask = ENTRY_EVENTMASK;
315     entry->item = createWindow(menu->items, CWEventMask, &attrib);
316     entry->submenu_pic = createWindow(menu->items, CWEventMask, &attrib);
317     XMapWindow(ob_display, entry->item);
318     XMapWindow(ob_display, entry->submenu_pic);
319
320     entry->a_item = entry->a_disabled = entry->a_hilite = entry->a_submenu
321         = NULL;
322
323     menu->invalid = TRUE;
324
325     g_hash_table_insert(window_map, &entry->item, menu);
326     g_hash_table_insert(window_map, &entry->submenu_pic, menu);
327 }
328
329 void menu_show(char *name, int x, int y, ObClient *client)
330 {
331     ObMenu *self;
332   
333     self = g_hash_table_lookup(menu_hash, name);
334     if (!self) {
335         g_warning("Attempted to show menu '%s' but it does not exist.",
336                   name);
337         return;
338     }
339
340     menu_show_full(self, x, y, client);
341 }  
342
343 void menu_show_full(ObMenu *self, int x, int y, ObClient *client)
344 {
345     g_assert(self != NULL);
346        
347     self->update(self);
348     
349     self->client = client;
350
351     if (!self->shown) {
352         if (!(self->parent && self->parent->shown)) {
353             grab_pointer(TRUE, None);
354             grab_keyboard(TRUE);
355         }
356         menu_visible = g_list_append(menu_visible, self);
357     }
358
359     menu_control_show(self, x, y, client);
360 }
361
362 void menu_hide(ObMenu *self) {
363     if (self->shown) {
364         XUnmapWindow(ob_display, self->frame);
365         self->shown = FALSE;
366         if (self->open_submenu)
367             self->open_submenu->hide(self->open_submenu);
368         if (self->parent && self->parent->open_submenu == self) {
369             self->parent->open_submenu = NULL;
370         }
371
372         if (!(self->parent && self->parent->shown)) {
373             grab_keyboard(FALSE);
374             grab_pointer(FALSE, None);
375         }
376         menu_visible = g_list_remove(menu_visible, self);
377         if (self->over) {
378             ((ObMenuEntry *)self->over->data)->hilite = FALSE;
379             menu_entry_render(self->over->data);
380             self->over = NULL;
381         }
382     }
383 }
384
385 void menu_clear(ObMenu *self) {
386     GList *it;
387   
388     for (it = self->entries; it; it = it->next) {
389         ObMenuEntry *entry = it->data;
390         menu_entry_free(entry);
391     }
392     self->entries = NULL;
393     self->invalid = TRUE;
394 }
395
396
397 ObMenuEntry *menu_find_entry(ObMenu *menu, Window win)
398 {
399     GList *it;
400
401     for (it = menu->entries; it; it = it->next) {
402         ObMenuEntry *entry = it->data;
403         if (entry->item == win)
404             return entry;
405     }
406     return NULL;
407 }
408
409 ObMenuEntry *menu_find_entry_by_submenu(ObMenu *menu, ObMenu *submenu)
410 {
411     GList *it;
412
413     for (it = menu->entries; it; it = it->next) {
414         ObMenuEntry *entry = it->data;
415         if (entry->submenu == submenu)
416             return entry;
417     }
418     return NULL;
419 }
420
421 ObMenuEntry *menu_find_entry_by_pos(ObMenu *menu, int x, int y)
422 {
423     if (x < 0 || x >= menu->size.width || y < 0 || y >= menu->size.height)
424         return NULL;
425
426     y -= menu->title_h + ob_rr_theme->bwidth;
427     if (y < 0) return NULL;
428     
429     ob_debug("%d %p\n", y/menu->item_h,
430              g_list_nth_data(menu->entries, y / menu->item_h));
431     return g_list_nth_data(menu->entries, y / menu->item_h);
432 }
433
434 void menu_entry_fire(ObMenuEntry *self, unsigned int button, unsigned int x,
435                      unsigned int y)
436 {
437     ObMenu *m;
438
439     /* ignore wheel scrolling */
440     if (button == 4 || button == 5) return;
441
442     if (self->action) {
443         self->action->data.any.c = self->parent->client;
444         self->action->func(&self->action->data);
445
446         /* hide the whole thing */
447         m = self->parent;
448         while (m->parent) m = m->parent;
449         m->hide(m);
450     }
451 }
452
453 /* 
454    Default menu controller action for showing.
455 */
456
457 void menu_control_show(ObMenu *self, int x, int y, ObClient *client)
458 {
459     guint i;
460     Rect *a = NULL;
461
462     g_assert(!self->invalid);
463     
464     for (i = 0; i < screen_num_monitors; ++i) {
465         a = screen_physical_area_monitor(i);
466         if (RECT_CONTAINS(*a, x, y))
467             break;
468     }
469     g_assert(a != NULL);
470     self->xin_area = i;
471
472     POINT_SET(self->location,
473               MIN(x, a->x + a->width - 1 - self->size.width), 
474               MIN(y, a->y + a->height - 1 - self->size.height));
475     XMoveWindow(ob_display, self->frame, self->location.x, self->location.y);
476
477     if (!self->shown) {
478         XMapWindow(ob_display, self->frame);
479         stacking_raise(MENU_AS_WINDOW(self));
480         self->shown = TRUE;
481     } else if (self->shown && self->open_submenu) {
482         self->open_submenu->hide(self->open_submenu);
483     }
484 }
485
486 void menu_control_mouseover(ObMenuEntry *self, gboolean enter)
487 {
488     int x;
489     Rect *a;
490     ObMenuEntry *e;
491
492     g_assert(self != NULL);
493     
494     if (enter) {
495         /* TODO: we prolly don't need open_submenu */
496         if (self->parent->open_submenu && self->submenu 
497             != self->parent->open_submenu)
498         {
499             e = (ObMenuEntry *) self->parent->over->data;
500             e->hilite = FALSE;
501             menu_entry_render(e);
502             self->parent->open_submenu->hide(self->parent->open_submenu);
503         }
504         
505         if (self->submenu && self->parent->open_submenu != self->submenu) {
506             self->parent->open_submenu = self->submenu;
507
508             /* shouldn't be invalid since it must be displayed */
509             g_assert(!self->parent->invalid);
510             /* TODO: I don't understand why these bevels should be here.
511                Something must be wrong in the width calculation */
512             x = self->parent->location.x + self->parent->size.width + 
513                 ob_rr_theme->bwidth - ob_rr_theme->menu_overlap;
514
515             /* need to get the width. is this bad?*/
516             self->parent->update(self->submenu);
517
518             a = screen_physical_area_monitor(self->parent->xin_area);
519
520             if (self->submenu->size.width + x >= a->x + a->width) {
521                 int newparentx = a->x + a->width
522                     - self->submenu->size.width
523                     - self->parent->size.width
524                     - ob_rr_theme->bwidth
525                     - ob_rr_theme->menu_overlap;
526                 
527                 x = a->x + a->width - self->submenu->size.width
528                     - ob_rr_theme->menu_overlap;
529                 XWarpPointer(ob_display, None, None, 0, 0, 0, 0,
530                              newparentx - self->parent->location.x, 0);
531
532                 menu_show_full(self->parent, newparentx,
533                                self->parent->location.y, self->parent->client);
534             }
535             
536             menu_show_full(self->submenu, x,
537                            self->parent->location.y + self->y,
538                            self->parent->client);
539         }
540         self->hilite = TRUE;
541         self->parent->over = g_list_find(self->parent->entries, self);
542         
543     } else
544         self->hilite = FALSE;
545     
546     menu_entry_render(self);
547 }
548
549 void menu_control_keyboard_nav(unsigned int key)
550 {
551     static ObMenu *current_menu = NULL;
552     ObMenuEntry *e = NULL;
553
554     ObKey obkey = OB_NUM_KEYS;
555
556     /* hrmm. could be fixed */
557     if (key == ob_keycode(OB_KEY_DOWN))
558         obkey = OB_KEY_DOWN;
559     else if (key == ob_keycode(OB_KEY_UP))
560         obkey = OB_KEY_UP;
561     else if (key == ob_keycode(OB_KEY_RIGHT)) /* fuck */
562         obkey = OB_KEY_RIGHT;
563     else if (key == ob_keycode(OB_KEY_LEFT)) /* users */
564         obkey = OB_KEY_LEFT;
565     else if (key == ob_keycode(OB_KEY_RETURN))
566         obkey = OB_KEY_RETURN;
567
568     
569     if (current_menu == NULL)
570         current_menu = menu_visible->data;
571     
572     switch (obkey) {
573     case OB_KEY_DOWN: {
574         if (current_menu->over) {
575             current_menu->mouseover(current_menu->over->data, FALSE);
576             current_menu->over = (current_menu->over->next != NULL ?
577                           current_menu->over->next :
578                           current_menu->entries);
579         }
580         else
581             current_menu->over = current_menu->entries;
582
583         if (current_menu->over)
584             current_menu->mouseover(current_menu->over->data, TRUE);
585         
586         break;
587     }
588     case OB_KEY_UP: {
589         if (current_menu->over) {
590             current_menu->mouseover(current_menu->over->data, FALSE);
591             current_menu->over = (current_menu->over->prev != NULL ?
592                           current_menu->over->prev :
593                 g_list_last(current_menu->entries));
594         } else
595             current_menu->over = g_list_last(current_menu->entries);
596
597         if (current_menu->over)
598             current_menu->mouseover(current_menu->over->data, TRUE);
599         
600         break;
601     }
602     case OB_KEY_RIGHT: {
603         if (current_menu->over == NULL)
604             return;
605         e = (ObMenuEntry *)current_menu->over->data;
606         if (e->submenu) {
607             current_menu->mouseover(e, TRUE);
608             current_menu = e->submenu;
609             current_menu->over = current_menu->entries;
610             if (current_menu->over)
611                 current_menu->mouseover(current_menu->over->data, TRUE);
612         }
613         break;
614     }
615
616     case OB_KEY_RETURN: {
617         if (current_menu->over == NULL)
618             return;
619         e = (ObMenuEntry *)current_menu->over->data;
620
621         current_menu->mouseover(e, FALSE);
622         current_menu->over = NULL;
623         /* zero is enter */
624         menu_entry_fire(e, 0, 0, 0);
625     }
626         
627     case OB_KEY_LEFT: {
628         if (current_menu->over != NULL) {
629             current_menu->mouseover(current_menu->over->data, FALSE);
630             current_menu->over = NULL;
631         }
632         
633         current_menu->hide(current_menu);
634
635         if (current_menu->parent)
636             current_menu = current_menu->parent;
637         
638         break;
639     }
640     default:
641         ((ObMenu *)menu_visible->data)->hide(menu_visible->data);
642         current_menu = NULL;
643     }
644     return;
645 }
646
647 void menu_noop()
648 {
649     /* This noop brought to you by OLS 2003 Email Garden. */
650 }