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