]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/menuframe.c
add the PointerMotionHintMask everywhere, we dont need every mouse event
[mikachu/openbox.git] / openbox / menuframe.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    menuframe.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "menuframe.h"
21 #include "client.h"
22 #include "menu.h"
23 #include "screen.h"
24 #include "grab.h"
25 #include "openbox.h"
26 #include "mainloop.h"
27 #include "config.h"
28 #include "render/theme.h"
29
30 #define PADDING 2
31 #define SEPARATOR_HEIGHT 3
32 #define MAX_MENU_WIDTH 400
33
34 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | \
35                          PointerMotionHintMask | \
36                          EnterWindowMask | LeaveWindowMask)
37 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
38                          ButtonPressMask | ButtonReleaseMask)
39
40 GList *menu_frame_visible;
41
42 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
43                                               ObMenuFrame *frame);
44 static void menu_entry_frame_free(ObMenuEntryFrame *self);
45 static void menu_frame_render(ObMenuFrame *self);
46 static void menu_frame_update(ObMenuFrame *self);
47 static gboolean menu_entry_frame_submenu_timeout(gpointer data);
48
49 static Window createWindow(Window parent, gulong mask,
50                            XSetWindowAttributes *attrib)
51 {
52     return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
53                          RrDepth(ob_rr_inst), InputOutput,
54                          RrVisual(ob_rr_inst), mask, attrib);
55 }
56
57 GHashTable *menu_frame_map;
58
59 void menu_frame_startup(gboolean reconfig)
60 {
61     if (reconfig) return;
62
63     menu_frame_map = g_hash_table_new(g_int_hash, g_int_equal);
64 }
65
66 void menu_frame_shutdown(gboolean reconfig)
67 {
68     if (reconfig) return;
69
70     g_hash_table_destroy(menu_frame_map);
71 }
72
73 ObMenuFrame* menu_frame_new(ObMenu *menu, ObClient *client)
74 {
75     ObMenuFrame *self;
76     XSetWindowAttributes attr;
77
78     self = g_new0(ObMenuFrame, 1);
79     self->type = Window_Menu;
80     self->menu = menu;
81     self->selected = NULL;
82     self->client = client;
83     self->direction_right = TRUE;
84
85     attr.event_mask = FRAME_EVENTMASK;
86     self->window = createWindow(RootWindow(ob_display, ob_screen),
87                                 CWEventMask, &attr);
88
89     self->a_title = RrAppearanceCopy(ob_rr_theme->a_menu_title);
90     self->a_items = RrAppearanceCopy(ob_rr_theme->a_menu);
91
92     stacking_add(MENU_AS_WINDOW(self));
93
94     return self;
95 }
96
97 void menu_frame_free(ObMenuFrame *self)
98 {
99     if (self) {
100         while (self->entries) {
101             menu_entry_frame_free(self->entries->data);
102             self->entries = g_list_delete_link(self->entries, self->entries);
103         }
104
105         stacking_remove(MENU_AS_WINDOW(self));
106
107         XDestroyWindow(ob_display, self->window);
108
109         RrAppearanceFree(self->a_items);
110         RrAppearanceFree(self->a_title);
111
112         g_free(self);
113     }
114 }
115
116 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
117                                               ObMenuFrame *frame)
118 {
119     ObMenuEntryFrame *self;
120     XSetWindowAttributes attr;
121
122     self = g_new0(ObMenuEntryFrame, 1);
123     self->entry = entry;
124     self->frame = frame;
125
126     attr.event_mask = ENTRY_EVENTMASK;
127     self->window = createWindow(self->frame->window, CWEventMask, &attr);
128     self->text = createWindow(self->window, 0, NULL);
129     g_hash_table_insert(menu_frame_map, &self->window, self);
130     g_hash_table_insert(menu_frame_map, &self->text, self);
131     if (entry->type == OB_MENU_ENTRY_TYPE_NORMAL) {
132         self->icon = createWindow(self->window, 0, NULL);
133         g_hash_table_insert(menu_frame_map, &self->icon, self);
134     }
135     if (entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
136         self->bullet = createWindow(self->window, 0, NULL);
137         g_hash_table_insert(menu_frame_map, &self->bullet, self);
138     }
139
140     XMapWindow(ob_display, self->window);
141     XMapWindow(ob_display, self->text);
142
143     self->a_normal = RrAppearanceCopy(ob_rr_theme->a_menu_normal);
144     self->a_disabled = RrAppearanceCopy(ob_rr_theme->a_menu_disabled);
145     self->a_selected = RrAppearanceCopy(ob_rr_theme->a_menu_selected);
146
147     if (entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR) {
148         self->a_separator = RrAppearanceCopy(ob_rr_theme->a_clear_tex);
149         self->a_separator->texture[0].type = RR_TEXTURE_LINE_ART;
150     } else {
151         self->a_icon = RrAppearanceCopy(ob_rr_theme->a_clear_tex);
152         self->a_icon->texture[0].type = RR_TEXTURE_RGBA;
153         self->a_mask = RrAppearanceCopy(ob_rr_theme->a_clear_tex);
154         self->a_mask->texture[0].type = RR_TEXTURE_MASK;
155         self->a_bullet_normal =
156             RrAppearanceCopy(ob_rr_theme->a_menu_bullet_normal);
157         self->a_bullet_selected =
158             RrAppearanceCopy(ob_rr_theme->a_menu_bullet_selected);
159     }
160
161     self->a_text_normal =
162         RrAppearanceCopy(ob_rr_theme->a_menu_text_normal);
163     self->a_text_disabled =
164         RrAppearanceCopy(ob_rr_theme->a_menu_text_disabled);
165     self->a_text_selected =
166         RrAppearanceCopy(ob_rr_theme->a_menu_text_selected);
167     self->a_text_title =
168         RrAppearanceCopy(ob_rr_theme->a_menu_text_title);
169
170     return self;
171 }
172
173 static void menu_entry_frame_free(ObMenuEntryFrame *self)
174 {
175     if (self) {
176         XDestroyWindow(ob_display, self->text);
177         XDestroyWindow(ob_display, self->window);
178         g_hash_table_remove(menu_frame_map, &self->text);
179         g_hash_table_remove(menu_frame_map, &self->window);
180         if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) {
181             XDestroyWindow(ob_display, self->icon);
182             g_hash_table_remove(menu_frame_map, &self->icon);
183         }
184         if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
185             XDestroyWindow(ob_display, self->bullet);
186             g_hash_table_remove(menu_frame_map, &self->bullet);
187         }
188
189         RrAppearanceFree(self->a_normal);
190         RrAppearanceFree(self->a_disabled);
191         RrAppearanceFree(self->a_selected);
192
193         RrAppearanceFree(self->a_separator);
194         RrAppearanceFree(self->a_icon);
195         RrAppearanceFree(self->a_mask);
196         RrAppearanceFree(self->a_text_normal);
197         RrAppearanceFree(self->a_text_disabled);
198         RrAppearanceFree(self->a_text_selected);
199         RrAppearanceFree(self->a_text_title);
200         RrAppearanceFree(self->a_bullet_normal);
201         RrAppearanceFree(self->a_bullet_selected);
202
203         g_free(self);
204     }
205 }
206
207 void menu_frame_move(ObMenuFrame *self, gint x, gint y)
208 {
209     RECT_SET_POINT(self->area, x, y);
210     XMoveWindow(ob_display, self->window, self->area.x, self->area.y);
211 }
212
213 void menu_frame_place_topmenu(ObMenuFrame *self, gint x, gint y)
214 {
215     if (self->client && x < 0 && y < 0) {
216         x = self->client->frame->area.x + self->client->frame->size.left;
217         y = self->client->frame->area.y + self->client->frame->size.top;
218     } else {
219         if (config_menu_middle)
220             y -= self->area.height / 2;
221     }
222     menu_frame_move(self, x, y);
223 }
224
225 void menu_frame_place_submenu(ObMenuFrame *self)
226 {
227     gint x, y;
228     gint overlap;
229     gint bwidth;
230
231     overlap = ob_rr_theme->menu_overlap;
232     bwidth = ob_rr_theme->mbwidth;
233
234     if (self->direction_right)
235         x = self->parent->area.x + self->parent->area.width - overlap - bwidth;
236     else
237         x = self->parent->area.x - self->area.width + overlap + bwidth;
238
239     y = self->parent->area.y + self->parent_entry->area.y;
240     if (config_menu_middle)
241         y -= (self->area.height - (bwidth * 2) - self->item_h) / 2;
242     else
243         y += overlap;
244
245     menu_frame_move(self, x, y);
246 }
247
248 void menu_frame_move_on_screen(ObMenuFrame *self, gint *dx, gint *dy)
249 {
250     Rect *a = NULL;
251     gint pos, half;
252
253     *dx = *dy = 0;
254
255     a = screen_physical_area_monitor(self->monitor);
256
257     half = g_list_length(self->entries) / 2;
258     pos = g_list_index(self->entries, self->selected);
259
260     /* if in the bottom half then check this stuff first, will keep the bottom
261        edge of the menu visible */
262     if (pos > half) {
263         *dx = MAX(*dx, a->x - self->area.x);
264         *dy = MAX(*dy, a->y - self->area.y);
265     }
266     *dx = MIN(*dx, (a->x + a->width) - (self->area.x + self->area.width));
267     *dy = MIN(*dy, (a->y + a->height) - (self->area.y + self->area.height));
268     /* if in the top half then check this stuff last, will keep the top
269        edge of the menu visible */
270     if (pos <= half) {
271         *dx = MAX(*dx, a->x - self->area.x);
272         *dy = MAX(*dy, a->y - self->area.y);
273     }
274 }
275
276 static void menu_entry_frame_render(ObMenuEntryFrame *self)
277 {
278     RrAppearance *item_a, *text_a;
279     gint th; /* temp */
280     ObMenu *sub;
281     ObMenuFrame *frame = self->frame;
282
283     switch (self->entry->type) {
284     case OB_MENU_ENTRY_TYPE_NORMAL:
285     case OB_MENU_ENTRY_TYPE_SUBMENU:
286         item_a = ((self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
287                    !self->entry->data.normal.enabled) ?
288                   self->a_disabled :
289                   (self == self->frame->selected ?
290                    self->a_selected :
291                    self->a_normal));
292         th = self->frame->item_h;
293         break;
294     case OB_MENU_ENTRY_TYPE_SEPARATOR:
295         if (self->entry->data.separator.label) {
296             item_a = self->frame->a_title;
297             th = ob_rr_theme->menu_title_height;
298         } else {
299             item_a = self->a_normal;
300             th = SEPARATOR_HEIGHT + 2*PADDING;
301         }
302         break;
303     default:
304         g_assert_not_reached();
305     }
306     RECT_SET_SIZE(self->area, self->frame->inner_w, th);
307     XResizeWindow(ob_display, self->window,
308                   self->area.width, self->area.height);
309     item_a->surface.parent = self->frame->a_items;
310     item_a->surface.parentx = self->area.x;
311     item_a->surface.parenty = self->area.y;
312     RrPaint(item_a, self->window, self->area.width, self->area.height);
313
314     switch (self->entry->type) {
315     case OB_MENU_ENTRY_TYPE_NORMAL:
316         text_a = (!self->entry->data.normal.enabled ?
317                   self->a_text_disabled :
318                   (self == self->frame->selected ?
319                    self->a_text_selected :
320                    self->a_text_normal));
321         text_a->texture[0].data.text.string = self->entry->data.normal.label;
322         break;
323     case OB_MENU_ENTRY_TYPE_SUBMENU:
324         text_a = (self == self->frame->selected ?
325                   self->a_text_selected :
326                   self->a_text_normal);
327         sub = self->entry->data.submenu.submenu;
328         text_a->texture[0].data.text.string = sub ? sub->title : "";
329         break;
330     case OB_MENU_ENTRY_TYPE_SEPARATOR:
331         if (self->entry->data.separator.label != NULL)
332             text_a = self->a_text_title;
333         else
334             text_a = self->a_text_normal;
335         break;
336     }
337
338     switch (self->entry->type) {
339     case OB_MENU_ENTRY_TYPE_NORMAL:
340         XMoveResizeWindow(ob_display, self->text,
341                           self->frame->text_x, PADDING,
342                           self->frame->text_w,
343                           self->frame->item_h - 2*PADDING);
344         text_a->surface.parent = item_a;
345         text_a->surface.parentx = self->frame->text_x;
346         text_a->surface.parenty = PADDING;
347         RrPaint(text_a, self->text, self->frame->text_w,
348                 self->frame->item_h - 2*PADDING);
349         break;
350     case OB_MENU_ENTRY_TYPE_SUBMENU:
351         XMoveResizeWindow(ob_display, self->text,
352                           self->frame->text_x, PADDING,
353                           self->frame->text_w - self->frame->item_h,
354                           self->frame->item_h - 2*PADDING);
355         text_a->surface.parent = item_a;
356         text_a->surface.parentx = self->frame->text_x;
357         text_a->surface.parenty = PADDING;
358         RrPaint(text_a, self->text, self->frame->text_w - self->frame->item_h,
359                 self->frame->item_h - 2*PADDING);
360         break;
361     case OB_MENU_ENTRY_TYPE_SEPARATOR:
362         if (self->entry->data.separator.label != NULL) {
363             /* labeled separator */
364             XMoveResizeWindow(ob_display, self->text,
365                               ob_rr_theme->paddingx, ob_rr_theme->paddingy,
366                               self->area.width - 2*ob_rr_theme->paddingx,
367                               ob_rr_theme->menu_title_height -
368                               2*ob_rr_theme->paddingy);
369             text_a->surface.parent = item_a;
370             text_a->surface.parentx = ob_rr_theme->paddingx;
371             text_a->surface.parenty = ob_rr_theme->paddingy;
372             RrPaint(text_a, self->text,
373                     self->area.width - 2*ob_rr_theme->paddingx,
374                     ob_rr_theme->menu_title_height -
375                     2*ob_rr_theme->paddingy);
376         } else {
377             /* unlabeled separaator */
378             XMoveResizeWindow(ob_display, self->text, PADDING, PADDING,
379                               self->area.width - 2*PADDING, SEPARATOR_HEIGHT);
380             self->a_separator->surface.parent = item_a;
381             self->a_separator->surface.parentx = PADDING;
382             self->a_separator->surface.parenty = PADDING;
383             self->a_separator->texture[0].data.lineart.color =
384                 text_a->texture[0].data.text.color;
385             self->a_separator->texture[0].data.lineart.x1 = 2*PADDING;
386             self->a_separator->texture[0].data.lineart.y1 = SEPARATOR_HEIGHT/2;
387             self->a_separator->texture[0].data.lineart.x2 =
388                 self->area.width - 4*PADDING;
389             self->a_separator->texture[0].data.lineart.y2 = SEPARATOR_HEIGHT/2;
390             RrPaint(self->a_separator, self->text,
391                     self->area.width - 2*PADDING, SEPARATOR_HEIGHT);
392         }
393         break;
394     }
395
396     if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
397         self->entry->data.normal.icon_data)
398     {
399         XMoveResizeWindow(ob_display, self->icon,
400                           PADDING, frame->item_margin.top,
401                           self->frame->item_h - frame->item_margin.top
402                           - frame->item_margin.bottom,
403                           self->frame->item_h - frame->item_margin.top
404                           - frame->item_margin.bottom);
405         self->a_icon->texture[0].data.rgba.width =
406             self->entry->data.normal.icon_width;
407         self->a_icon->texture[0].data.rgba.height =
408             self->entry->data.normal.icon_height;
409         self->a_icon->texture[0].data.rgba.data =
410             self->entry->data.normal.icon_data;
411         self->a_icon->surface.parent = item_a;
412         self->a_icon->surface.parentx = PADDING;
413         self->a_icon->surface.parenty = frame->item_margin.top;
414         RrPaint(self->a_icon, self->icon,
415                 self->frame->item_h - frame->item_margin.top
416                 - frame->item_margin.bottom,
417                 self->frame->item_h - frame->item_margin.top
418                 - frame->item_margin.bottom);
419         XMapWindow(ob_display, self->icon);
420     } else if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
421                self->entry->data.normal.mask)
422     {
423         RrColor *c;
424
425         XMoveResizeWindow(ob_display, self->icon,
426                           PADDING, frame->item_margin.top,
427                           self->frame->item_h - frame->item_margin.top
428                           - frame->item_margin.bottom,
429                           self->frame->item_h - frame->item_margin.top
430                           - frame->item_margin.bottom);
431         self->a_mask->texture[0].data.mask.mask =
432             self->entry->data.normal.mask;
433
434         c = ((self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
435               !self->entry->data.normal.enabled) ?
436              self->entry->data.normal.mask_disabled_color :
437              (self == self->frame->selected ?
438               self->entry->data.normal.mask_selected_color :
439               self->entry->data.normal.mask_normal_color));
440         self->a_mask->texture[0].data.mask.color = c;
441
442         self->a_mask->surface.parent = item_a;
443         self->a_mask->surface.parentx = PADDING;
444         self->a_mask->surface.parenty = frame->item_margin.top;
445         RrPaint(self->a_mask, self->icon,
446                 self->frame->item_h - frame->item_margin.top
447                 - frame->item_margin.bottom,
448                 self->frame->item_h - frame->item_margin.top
449                 - frame->item_margin.bottom);
450         XMapWindow(ob_display, self->icon);
451     } else
452         XUnmapWindow(ob_display, self->icon);
453
454     if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
455         RrAppearance *bullet_a;
456         XMoveResizeWindow(ob_display, self->bullet,
457                           self->frame->text_x + self->frame->text_w
458                           - self->frame->item_h + PADDING, PADDING,
459                           self->frame->item_h - 2*PADDING,
460                           self->frame->item_h - 2*PADDING);
461         bullet_a = (self == self->frame->selected ?
462                     self->a_bullet_selected :
463                     self->a_bullet_normal);
464         bullet_a->surface.parent = item_a;
465         bullet_a->surface.parentx =
466             self->frame->text_x + self->frame->text_w - self->frame->item_h
467             + PADDING;
468         bullet_a->surface.parenty = PADDING;
469         RrPaint(bullet_a, self->bullet,
470                 self->frame->item_h - 2*PADDING,
471                 self->frame->item_h - 2*PADDING);
472         XMapWindow(ob_display, self->bullet);
473     } else
474         XUnmapWindow(ob_display, self->bullet);
475
476     XFlush(ob_display);
477 }
478
479 static void menu_frame_render(ObMenuFrame *self)
480 {
481     gint w = 0, h = 0;
482     gint tw, th; /* temps */
483     GList *it;
484     gboolean has_icon = FALSE;
485     ObMenu *sub;
486     ObMenuEntryFrame *e;
487
488     XSetWindowBorderWidth(ob_display, self->window, ob_rr_theme->mbwidth);
489     XSetWindowBorder(ob_display, self->window,
490                      RrColorPixel(ob_rr_theme->menu_b_color));
491
492     /* find text dimensions */
493
494     STRUT_SET(self->item_margin, 0, 0, 0, 0);
495
496     if (self->entries) {
497         ObMenuEntryFrame *e = self->entries->data;
498         gint l, t, r, b;
499
500         e->a_text_normal->texture[0].data.text.string = "";
501         RrMinsize(e->a_text_normal, &tw, &th);
502         tw += 2*PADDING;
503         th += 2*PADDING;
504         self->item_h = th;
505
506         RrMargins(e->a_normal, &l, &t, &r, &b);
507         STRUT_SET(self->item_margin,
508                   MAX(self->item_margin.left, l),
509                   MAX(self->item_margin.top, t),
510                   MAX(self->item_margin.right, r),
511                   MAX(self->item_margin.bottom, b));
512         RrMargins(e->a_selected, &l, &t, &r, &b);
513         STRUT_SET(self->item_margin,
514                   MAX(self->item_margin.left, l),
515                   MAX(self->item_margin.top, t),
516                   MAX(self->item_margin.right, r),
517                   MAX(self->item_margin.bottom, b));
518         RrMargins(e->a_disabled, &l, &t, &r, &b);
519         STRUT_SET(self->item_margin,
520                   MAX(self->item_margin.left, l),
521                   MAX(self->item_margin.top, t),
522                   MAX(self->item_margin.right, r),
523                   MAX(self->item_margin.bottom, b));
524     } else
525         self->item_h = 0;
526
527     /* render the entries */
528
529     for (it = self->entries; it; it = g_list_next(it)) {
530         RrAppearance *text_a;
531         e = it->data;
532
533         /* if the first entry is a labeled separator, then make its border
534            overlap with the menu's outside border */
535         if (it == self->entries &&
536             e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
537             e->entry->data.separator.label)
538         {
539             h -= ob_rr_theme->mbwidth;
540         }
541
542         if (e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
543             e->entry->data.separator.label)
544         {
545             e->border = ob_rr_theme->mbwidth;
546         }
547
548         RECT_SET_POINT(e->area, 0, h+e->border);
549         XMoveWindow(ob_display, e->window, e->area.x-e->border, e->area.y-e->border);
550         XSetWindowBorderWidth(ob_display, e->window, e->border);
551         XSetWindowBorder(ob_display, e->window,
552                          RrColorPixel(ob_rr_theme->menu_b_color));
553
554         text_a = ((e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
555                    !e->entry->data.normal.enabled) ?
556                   e->a_text_disabled :
557                   (e == self->selected ?
558                    e->a_text_selected :
559                    e->a_text_normal));
560         switch (e->entry->type) {
561         case OB_MENU_ENTRY_TYPE_NORMAL:
562             text_a->texture[0].data.text.string = e->entry->data.normal.label;
563             RrMinsize(text_a, &tw, &th);
564             tw = MIN(tw, MAX_MENU_WIDTH);
565
566             if (e->entry->data.normal.icon_data ||
567                 e->entry->data.normal.mask)
568                 has_icon = TRUE;
569             break;
570         case OB_MENU_ENTRY_TYPE_SUBMENU:
571             sub = e->entry->data.submenu.submenu;
572             text_a->texture[0].data.text.string = sub ? sub->title : "";
573             RrMinsize(text_a, &tw, &th);
574             tw = MIN(tw, MAX_MENU_WIDTH);
575
576             if (e->entry->data.normal.icon_data ||
577                 e->entry->data.normal.mask)
578                 has_icon = TRUE;
579
580             tw += self->item_h - PADDING;
581             break;
582         case OB_MENU_ENTRY_TYPE_SEPARATOR:
583             if (e->entry->data.separator.label != NULL) {
584                 e->a_text_title->texture[0].data.text.string =
585                     e->entry->data.separator.label;
586                 RrMinsize(e->a_text_title, &tw, &th);
587                 tw = MIN(tw, MAX_MENU_WIDTH);
588                 th = ob_rr_theme->menu_title_height +
589                     (ob_rr_theme->mbwidth - PADDING) *2;
590             } else {
591                 tw = 0;
592                 th = SEPARATOR_HEIGHT;
593             }
594             break;
595         }
596         tw += 2*PADDING;
597         th += 2*PADDING;
598         w = MAX(w, tw);
599         h += th;
600     }
601
602     /* if the last entry is a labeled separator, then make its border
603        overlap with the menu's outside border */
604     it = g_list_last(self->entries);
605     e = it ? it->data : NULL;
606     if (e && e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
607         e->entry->data.separator.label)
608     {
609         h -= ob_rr_theme->mbwidth;
610     }
611
612     self->text_x = PADDING;
613     self->text_w = w;
614
615     if (self->entries) {
616         if (has_icon) {
617             w += self->item_h + PADDING;
618             self->text_x += self->item_h + PADDING;
619         }
620     }
621
622     if (!w) w = 10;
623     if (!h) h = 3;
624
625     XResizeWindow(ob_display, self->window, w, h);
626
627     self->inner_w = w;
628
629     RrPaint(self->a_items, self->window, w, h);
630
631     for (it = self->entries; it; it = g_list_next(it))
632         menu_entry_frame_render(it->data);
633
634     w += ob_rr_theme->mbwidth * 2;
635     h += ob_rr_theme->mbwidth * 2;
636
637     RECT_SET_SIZE(self->area, w, h);
638
639     XFlush(ob_display);
640 }
641
642 static void menu_frame_update(ObMenuFrame *self)
643 {
644     GList *mit, *fit;
645
646     menu_pipe_execute(self->menu);
647     menu_find_submenus(self->menu);
648
649     self->selected = NULL;
650
651     for (mit = self->menu->entries, fit = self->entries; mit && fit;
652          mit = g_list_next(mit), fit = g_list_next(fit))
653     {
654         ObMenuEntryFrame *f = fit->data;
655         f->entry = mit->data;
656     }
657
658     while (mit) {
659         ObMenuEntryFrame *e = menu_entry_frame_new(mit->data, self);
660         self->entries = g_list_append(self->entries, e);
661         mit = g_list_next(mit);
662     }
663     
664     while (fit) {
665         GList *n = g_list_next(fit);
666         menu_entry_frame_free(fit->data);
667         self->entries = g_list_delete_link(self->entries, fit);
668         fit = n;
669     }
670
671     menu_frame_render(self);
672 }
673
674 static gboolean menu_frame_is_visible(ObMenuFrame *self)
675 {
676     return !!(g_list_find(menu_frame_visible, self));
677 }
678
679 static gboolean menu_frame_show(ObMenuFrame *self)
680 {
681     GList *it;
682
683     if (menu_frame_visible == NULL) {
684         /* no menus shown yet */
685         if (!grab_pointer(TRUE, TRUE, OB_CURSOR_POINTER))
686             return FALSE;
687         if (!grab_keyboard(TRUE)) {
688             grab_pointer(FALSE, TRUE, OB_CURSOR_POINTER);
689             return FALSE;
690         }
691     }
692
693     /* determine if the underlying menu is already visible */
694     for (it = menu_frame_visible; it; it = g_list_next(it)) {
695         ObMenuFrame *f = it->data;
696         if (f->menu == self->menu)
697             break;
698     }
699     if (!it) {
700         if (self->menu->update_func)
701             self->menu->update_func(self, self->menu->data);
702     }
703
704     menu_frame_update(self);
705
706     menu_frame_visible = g_list_prepend(menu_frame_visible, self);
707
708     return TRUE;
709 }
710
711 gboolean menu_frame_show_topmenu(ObMenuFrame *self, gint x, gint y)
712 {
713     gint dx, dy;
714     guint i;
715
716     if (menu_frame_is_visible(self))
717         return TRUE;
718     if (!menu_frame_show(self))
719         return FALSE;
720
721     menu_frame_place_topmenu(self, x, y);
722
723     /* find the monitor the menu is on */
724     for (i = 0; i < screen_num_monitors; ++i) {
725         Rect *a = screen_physical_area_monitor(i);
726         if (RECT_CONTAINS(*a, x, y)) {
727             self->monitor = i;
728             break;
729         }
730     }
731
732     menu_frame_move_on_screen(self, &dx, &dy);
733     menu_frame_move(self, self->area.x + dx, self->area.y + dy);
734
735     XMapWindow(ob_display, self->window);
736
737     return TRUE;
738 }
739
740 gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent,
741                                  ObMenuEntryFrame *parent_entry)
742 {
743     ObMenuEntryFrame *e;
744     gint dx, dy;
745
746     if (menu_frame_is_visible(self))
747         return TRUE;
748
749     self->monitor = parent->monitor;
750     self->parent = parent;
751     self->parent_entry = parent_entry;
752
753     /* set up parent's child to be us */
754     if (parent->child)
755         menu_frame_hide(parent->child);
756     parent->child = self;
757
758     if (!menu_frame_show(self))
759         return FALSE;
760
761     menu_frame_place_submenu(self);
762     menu_frame_move_on_screen(self, &dx, &dy);
763
764     if (dx == 0) {
765         menu_frame_move(self, self->area.x, self->area.y + dy);
766     } else {
767         gboolean dir;
768
769         /* flip the direction in which we're placing submenus */
770         if (dx > 0)
771             dir = TRUE;
772         else
773             dir = FALSE;
774
775         /* if it changed, then replace the menu on the opposite side,
776            and try keep it on the screen too */
777         if (dir != self->direction_right) {
778             self->direction_right = dir;
779             menu_frame_place_submenu(self);
780             menu_frame_move_on_screen(self, &dx, &dy);
781             menu_frame_move(self, self->area.x + dx, self->area.y + dy);
782         }
783     }
784
785     XMapWindow(ob_display, self->window);
786
787     if (screen_pointer_pos(&dx, &dy) && (e = menu_entry_frame_under(dx, dy)) &&
788         e->frame == self)
789         ++e->ignore_enters;
790
791     return TRUE;
792 }
793
794 void menu_frame_hide(ObMenuFrame *self)
795 {
796     GList *it = g_list_find(menu_frame_visible, self);
797
798     if (!it)
799         return;
800
801     if (self->child)
802         menu_frame_hide(self->child);
803
804     if (self->parent)
805         self->parent->child = NULL;
806     self->parent = NULL;
807     self->parent_entry = NULL;
808
809     menu_frame_visible = g_list_delete_link(menu_frame_visible, it);
810
811     if (menu_frame_visible == NULL) {
812         /* last menu shown */
813         grab_pointer(FALSE, TRUE, OB_CURSOR_NONE);
814         grab_keyboard(FALSE);
815     }
816
817     XUnmapWindow(ob_display, self->window);
818
819     menu_frame_free(self);
820 }
821
822 void menu_frame_hide_all()
823 {
824     GList *it;
825
826     if (config_submenu_show_delay) {
827         /* remove any submenu open requests */
828         ob_main_loop_timeout_remove(ob_main_loop,
829                                     menu_entry_frame_submenu_timeout);
830     }
831     if ((it = g_list_last(menu_frame_visible)))
832         menu_frame_hide(it->data);
833 }
834
835 void menu_frame_hide_all_client(ObClient *client)
836 {
837     GList *it = g_list_last(menu_frame_visible);
838     if (it) {
839         ObMenuFrame *f = it->data;
840         if (f->client == client)
841             menu_frame_hide(f);
842     }
843 }
844
845
846 ObMenuFrame* menu_frame_under(gint x, gint y)
847 {
848     ObMenuFrame *ret = NULL;
849     GList *it;
850
851     for (it = menu_frame_visible; it; it = g_list_next(it)) {
852         ObMenuFrame *f = it->data;
853
854         if (RECT_CONTAINS(f->area, x, y)) {
855             ret = f;
856             break;
857         }
858     }
859     return ret;
860 }
861
862 ObMenuEntryFrame* menu_entry_frame_under(gint x, gint y)
863 {
864     ObMenuFrame *frame;
865     ObMenuEntryFrame *ret = NULL;
866     GList *it;
867
868     if ((frame = menu_frame_under(x, y))) {
869         x -= ob_rr_theme->mbwidth + frame->area.x;
870         y -= ob_rr_theme->mbwidth + frame->area.y;
871
872         for (it = frame->entries; it; it = g_list_next(it)) {
873             ObMenuEntryFrame *e = it->data;
874
875             if (RECT_CONTAINS(e->area, x, y)) {
876                 ret = e;            
877                 break;
878             }
879         }
880     }
881     return ret;
882 }
883
884 static gboolean menu_entry_frame_submenu_timeout(gpointer data)
885 {
886     menu_entry_frame_show_submenu((ObMenuEntryFrame*)data);
887     return FALSE;
888 }
889
890 void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry)
891 {
892     ObMenuEntryFrame *old = self->selected;
893     ObMenuFrame *oldchild = self->child;
894
895     if (entry && entry->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR)
896         entry = old;
897
898     if (old == entry) return;
899    
900     if (config_submenu_show_delay) { 
901         /* remove any submenu open requests */
902         ob_main_loop_timeout_remove(ob_main_loop,
903                                     menu_entry_frame_submenu_timeout);
904     }
905
906     self->selected = entry;
907
908     if (old)
909         menu_entry_frame_render(old);
910     if (oldchild)
911         menu_frame_hide(oldchild);
912
913     if (self->selected) {
914         menu_entry_frame_render(self->selected);
915
916         if (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
917             if (config_submenu_show_delay) {
918                 /* initiate a new submenu open request */
919                 ob_main_loop_timeout_add(ob_main_loop,
920                                          config_submenu_show_delay * 1000,
921                                          menu_entry_frame_submenu_timeout,
922                                          self->selected, g_direct_equal,
923                                          NULL);
924             } else {
925                 menu_entry_frame_show_submenu(self->selected);
926             }
927         }
928     }
929 }
930
931 void menu_entry_frame_show_submenu(ObMenuEntryFrame *self)
932 {
933     ObMenuFrame *f;
934
935     if (!self->entry->data.submenu.submenu) return;
936
937     f = menu_frame_new(self->entry->data.submenu.submenu,
938                        self->frame->client);
939     /* pass our direction on to our child */
940     f->direction_right = self->frame->direction_right;
941
942     menu_frame_show_submenu(f, self->frame, self);
943 }
944
945 void menu_entry_frame_execute(ObMenuEntryFrame *self, guint state, Time time)
946 {
947     if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
948         self->entry->data.normal.enabled)
949     {
950         /* grab all this shizzle, cuz when the menu gets hidden, 'self'
951            gets freed */
952         ObMenuEntry *entry = self->entry;
953         ObMenuExecuteFunc func = self->frame->menu->execute_func;
954         gpointer data = self->frame->menu->data;
955         GSList *acts = self->entry->data.normal.actions;
956         ObClient *client = self->frame->client;
957
958         /* release grabs before executing the shit */
959         if (!(state & ControlMask))
960             menu_frame_hide_all();
961
962         if (func)
963             func(entry, state, data, time);
964         else
965             action_run(acts, client, state, time);
966     }
967 }
968
969 void menu_frame_select_previous(ObMenuFrame *self)
970 {
971     GList *it = NULL, *start;
972
973     if (self->entries) {
974         start = it = g_list_find(self->entries, self->selected);
975         while (TRUE) {
976             ObMenuEntryFrame *e;
977
978             it = it ? g_list_previous(it) : g_list_last(self->entries);
979             if (it == start)
980                 break;
981
982             if (it) {
983                 e = it->data;
984                 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
985                     break;
986                 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
987                     e->entry->data.normal.enabled)
988                     break;
989             }
990         }
991     }
992     menu_frame_select(self, it ? it->data : NULL);
993 }
994
995 void menu_frame_select_next(ObMenuFrame *self)
996 {
997     GList *it = NULL, *start;
998
999     if (self->entries) {
1000         start = it = g_list_find(self->entries, self->selected);
1001         while (TRUE) {
1002             ObMenuEntryFrame *e;
1003
1004             it = it ? g_list_next(it) : self->entries;
1005             if (it == start)
1006                 break;
1007
1008             if (it) {
1009                 e = it->data;
1010                 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1011                     break;
1012                 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1013                     e->entry->data.normal.enabled)
1014                     break;
1015             }
1016         }
1017     }
1018     menu_frame_select(self, it ? it->data : NULL);
1019 }