1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 menuframe.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
20 #include "menuframe.h"
25 #include "action_list.h"
26 #include "action_list_run.h"
32 #include "obt/keyboard.h"
33 #include "obrender/theme.h"
36 #define MAX_MENU_WIDTH 400
38 #define ITEM_HEIGHT (ob_rr_theme->menu_font_height + 2*PADDING)
40 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\
42 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
43 ButtonPressMask | ButtonReleaseMask)
45 GList *menu_frame_visible;
46 GHashTable *menu_frame_map;
48 static RrAppearance *a_sep;
49 static guint submenu_show_timer = 0;
50 static guint submenu_hide_timer = 0;
52 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
54 static void menu_entry_frame_free(ObMenuEntryFrame *self);
55 static void menu_frame_update(ObMenuFrame *self);
56 static gboolean submenu_show_timeout(gpointer data);
57 static void menu_frame_hide(ObMenuFrame *self);
59 static gboolean submenu_hide_timeout(gpointer data);
61 static Window createWindow(Window parent, gulong mask,
62 XSetWindowAttributes *attrib)
64 return XCreateWindow(obt_display, parent, 0, 0, 1, 1, 0,
65 RrDepth(ob_rr_inst), InputOutput,
66 RrVisual(ob_rr_inst), mask, attrib);
69 static void client_dest(ObClient *client, gpointer data)
73 /* menus can be associated with a client, so null those refs since
74 we are disappearing now */
75 for (it = menu_frame_visible; it; it = g_list_next(it)) {
76 ObMenuFrame *f = it->data;
77 if (f->client == client)
82 void menu_frame_startup(gboolean reconfig)
86 a_sep = RrAppearanceCopy(ob_rr_theme->a_clear);
87 RrAppearanceAddTextures(a_sep, ob_rr_theme->menu_sep_width);
88 for (i = 0; i < ob_rr_theme->menu_sep_width; ++i) {
89 a_sep->texture[i].type = RR_TEXTURE_LINE_ART;
90 a_sep->texture[i].data.lineart.color =
91 ob_rr_theme->menu_sep_color;
96 client_add_destroy_notify(client_dest, NULL);
97 menu_frame_map = g_hash_table_new(g_int_hash, g_int_equal);
100 void menu_frame_shutdown(gboolean reconfig)
102 RrAppearanceFree(a_sep);
104 if (reconfig) return;
106 client_remove_destroy_notify(client_dest);
107 g_hash_table_destroy(menu_frame_map);
110 ObMenuFrame* menu_frame_new(ObMenu *menu, guint show_from, ObClient *client)
113 XSetWindowAttributes attr;
115 self = g_slice_new0(ObMenuFrame);
116 self->obwin.type = OB_WINDOW_CLASS_MENUFRAME;
118 self->selected = NULL;
119 self->client = client;
120 self->direction_right = TRUE;
121 self->show_from = show_from;
123 attr.event_mask = FRAME_EVENTMASK;
124 self->window = createWindow(obt_root(ob_screen),
127 /* make it a popup menu type window */
128 OBT_PROP_SET32(self->window, NET_WM_WINDOW_TYPE, ATOM,
129 OBT_PROP_ATOM(NET_WM_WINDOW_TYPE_POPUP_MENU));
131 XSetWindowBorderWidth(obt_display, self->window, ob_rr_theme->mbwidth);
132 XSetWindowBorder(obt_display, self->window,
133 RrColorPixel(ob_rr_theme->menu_border_color));
135 self->a_items = RrAppearanceCopy(ob_rr_theme->a_menu);
137 window_add(&self->window, MENUFRAME_AS_WINDOW(self));
138 stacking_add(MENUFRAME_AS_WINDOW(self));
143 void menu_frame_free(ObMenuFrame *self)
146 while (self->entries) {
147 menu_entry_frame_free(self->entries->data);
148 self->entries = g_list_delete_link(self->entries, self->entries);
151 stacking_remove(MENUFRAME_AS_WINDOW(self));
152 window_remove(self->window);
154 RrAppearanceFree(self->a_items);
156 XDestroyWindow(obt_display, self->window);
158 g_slice_free(ObMenuFrame, self);
162 ObtIC* menu_frame_ic(ObMenuFrame *self)
164 /* menus are always used through a grab right now, so they can always use
165 the grab input context */
166 return grab_input_context();
169 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
172 ObMenuEntryFrame *self;
173 XSetWindowAttributes attr;
175 self = g_slice_new0(ObMenuEntryFrame);
179 menu_entry_ref(entry);
181 attr.event_mask = ENTRY_EVENTMASK;
182 self->window = createWindow(self->frame->window, CWEventMask, &attr);
183 self->text = createWindow(self->window, 0, NULL);
184 g_hash_table_insert(menu_frame_map, &self->window, self);
185 g_hash_table_insert(menu_frame_map, &self->text, self);
186 if ((entry->type == OB_MENU_ENTRY_TYPE_NORMAL) ||
187 (entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)) {
188 self->icon = createWindow(self->window, 0, NULL);
189 g_hash_table_insert(menu_frame_map, &self->icon, self);
191 if (entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
192 self->bullet = createWindow(self->window, 0, NULL);
193 g_hash_table_insert(menu_frame_map, &self->bullet, self);
196 XMapWindow(obt_display, self->window);
197 XMapWindow(obt_display, self->text);
199 window_add(&self->window, MENUFRAME_AS_WINDOW(self->frame));
204 static void menu_entry_frame_free(ObMenuEntryFrame *self)
207 menu_entry_unref(self->entry);
209 window_remove(self->window);
211 XDestroyWindow(obt_display, self->text);
212 XDestroyWindow(obt_display, self->window);
213 g_hash_table_remove(menu_frame_map, &self->text);
214 g_hash_table_remove(menu_frame_map, &self->window);
215 if ((self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) ||
216 (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)) {
217 XDestroyWindow(obt_display, self->icon);
218 g_hash_table_remove(menu_frame_map, &self->icon);
220 if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
221 XDestroyWindow(obt_display, self->bullet);
222 g_hash_table_remove(menu_frame_map, &self->bullet);
225 g_slice_free(ObMenuEntryFrame, self);
229 void menu_frame_move(ObMenuFrame *self, gint x, gint y)
231 RECT_SET_POINT(self->area, x, y);
232 self->monitor = screen_find_monitor_point(x, y);
233 XMoveWindow(obt_display, self->window, self->area.x, self->area.y);
236 static void menu_frame_place_topmenu(ObMenuFrame *self, gint *x, gint *y)
240 if (config_menu_middle) {
244 *y -= self->area.height / 2;
246 /* try to the right of the cursor */
247 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
248 self->direction_right = TRUE;
250 /* try to the left of the cursor */
251 myx = *x - self->area.width;
252 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
253 self->direction_right = FALSE;
256 /* if didnt fit on either side so just use what it says */
258 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
259 self->direction_right = TRUE;
269 /* try to the bottom right of the cursor */
270 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
271 self->direction_right = TRUE;
272 if (dx != 0 || dy != 0) {
273 /* try to the bottom left of the cursor */
274 myx = *x - self->area.width;
276 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
277 self->direction_right = FALSE;
279 if (dx != 0 || dy != 0) {
280 /* try to the top right of the cursor */
282 myy = *y - self->area.height;
283 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
284 self->direction_right = TRUE;
286 if (dx != 0 || dy != 0) {
287 /* try to the top left of the cursor */
288 myx = *x - self->area.width;
289 myy = *y - self->area.height;
290 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
291 self->direction_right = FALSE;
293 if (dx != 0 || dy != 0) {
294 /* if didnt fit on either side so just use what it says */
297 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
298 self->direction_right = TRUE;
305 static void menu_frame_place_submenu(ObMenuFrame *self, gint *x, gint *y)
307 gint overlapx, overlapy;
310 overlapx = ob_rr_theme->menu_overlap_x;
311 overlapy = ob_rr_theme->menu_overlap_y;
312 bwidth = ob_rr_theme->mbwidth;
314 if (self->direction_right)
315 *x = self->parent->area.x + self->parent->area.width -
318 *x = self->parent->area.x - self->area.width + overlapx + bwidth;
320 *y = self->parent->area.y + self->parent_entry->area.y;
321 if (config_menu_middle)
322 *y -= (self->area.height - (bwidth * 2) - ITEM_HEIGHT) / 2;
327 void menu_frame_move_on_screen(ObMenuFrame *self, gint x, gint y,
330 const Rect *a = NULL;
331 Rect search = self->area;
332 gint pos, half, monitor;
335 RECT_SET_POINT(search, x, y);
338 monitor = self->parent->monitor;
340 monitor = screen_find_monitor(&search);
342 a = screen_physical_area_monitor(monitor);
344 half = g_list_length(self->entries) / 2;
345 pos = g_list_index(self->entries, self->selected);
347 /* if in the bottom half then check this stuff first, will keep the bottom
348 edge of the menu visible */
350 *dx = MAX(*dx, a->x - x);
351 *dy = MAX(*dy, a->y - y);
353 *dx = MIN(*dx, (a->x + a->width) - (x + self->area.width));
354 *dy = MIN(*dy, (a->y + a->height) - (y + self->area.height));
355 /* if in the top half then check this stuff last, will keep the top
356 edge of the menu visible */
358 *dx = MAX(*dx, a->x - x);
359 *dy = MAX(*dy, a->y - y);
363 static void menu_entry_frame_render(ObMenuEntryFrame *self)
365 RrAppearance *item_a, *text_a;
368 ObMenuFrame *frame = self->frame;
370 switch (self->entry->type) {
371 case OB_MENU_ENTRY_TYPE_NORMAL:
372 case OB_MENU_ENTRY_TYPE_SUBMENU:
373 item_a = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
374 !self->entry->data.normal.enabled ?
376 (self == self->frame->selected ?
377 ob_rr_theme->a_menu_disabled_selected :
378 ob_rr_theme->a_menu_disabled) :
380 (self == self->frame->selected ?
381 ob_rr_theme->a_menu_selected :
382 ob_rr_theme->a_menu_normal));
385 case OB_MENU_ENTRY_TYPE_SEPARATOR:
386 if (self->entry->data.separator.label) {
387 item_a = ob_rr_theme->a_menu_title;
388 th = ob_rr_theme->menu_title_height;
390 item_a = ob_rr_theme->a_menu_normal;
391 th = ob_rr_theme->menu_sep_width +
392 2*ob_rr_theme->menu_sep_paddingy;
396 g_assert_not_reached();
399 RECT_SET_SIZE(self->area, self->frame->inner_w, th);
400 XResizeWindow(obt_display, self->window,
401 self->area.width, self->area.height);
402 item_a->surface.parent = self->frame->a_items;
403 item_a->surface.parentx = self->area.x;
404 item_a->surface.parenty = self->area.y;
405 RrPaint(item_a, self->window, self->area.width, self->area.height);
407 switch (self->entry->type) {
408 case OB_MENU_ENTRY_TYPE_NORMAL:
409 text_a = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
410 !self->entry->data.normal.enabled ?
412 (self == self->frame->selected ?
413 ob_rr_theme->a_menu_text_disabled_selected :
414 ob_rr_theme->a_menu_text_disabled) :
416 (self == self->frame->selected ?
417 ob_rr_theme->a_menu_text_selected :
418 ob_rr_theme->a_menu_text_normal));
419 text_a->texture[0].data.text.string = self->entry->data.normal.label;
420 if (self->entry->data.normal.shortcut &&
421 (self->frame->menu->show_all_shortcuts ||
422 self->entry->data.normal.shortcut_always_show ||
423 self->entry->data.normal.shortcut_position > 0))
425 text_a->texture[0].data.text.shortcut = TRUE;
426 text_a->texture[0].data.text.shortcut_pos =
427 self->entry->data.normal.shortcut_position;
429 text_a->texture[0].data.text.shortcut = FALSE;
431 case OB_MENU_ENTRY_TYPE_SUBMENU:
432 text_a = (self == self->frame->selected ?
433 ob_rr_theme->a_menu_text_selected :
434 ob_rr_theme->a_menu_text_normal);
435 sub = self->entry->data.submenu.submenu;
436 text_a->texture[0].data.text.string = sub ? sub->title : "";
437 if (sub && sub->shortcut && (self->frame->menu->show_all_shortcuts ||
438 sub->shortcut_always_show ||
439 sub->shortcut_position > 0))
441 text_a->texture[0].data.text.shortcut = TRUE;
442 text_a->texture[0].data.text.shortcut_pos = sub->shortcut_position;
444 text_a->texture[0].data.text.shortcut = FALSE;
446 case OB_MENU_ENTRY_TYPE_SEPARATOR:
447 if (self->entry->data.separator.label != NULL) {
448 text_a = ob_rr_theme->a_menu_text_title;
449 text_a->texture[0].data.text.string =
450 self->entry->data.separator.label;
453 text_a = ob_rr_theme->a_menu_text_normal;
456 g_assert_not_reached();
459 switch (self->entry->type) {
460 case OB_MENU_ENTRY_TYPE_NORMAL:
461 XMoveResizeWindow(obt_display, self->text,
462 self->frame->text_x, PADDING,
464 ITEM_HEIGHT - 2*PADDING);
465 text_a->surface.parent = item_a;
466 text_a->surface.parentx = self->frame->text_x;
467 text_a->surface.parenty = PADDING;
468 RrPaint(text_a, self->text, self->frame->text_w,
469 ITEM_HEIGHT - 2*PADDING);
471 case OB_MENU_ENTRY_TYPE_SUBMENU:
472 XMoveResizeWindow(obt_display, self->text,
473 self->frame->text_x, PADDING,
474 self->frame->text_w - ITEM_HEIGHT,
475 ITEM_HEIGHT - 2*PADDING);
476 text_a->surface.parent = item_a;
477 text_a->surface.parentx = self->frame->text_x;
478 text_a->surface.parenty = PADDING;
479 RrPaint(text_a, self->text, self->frame->text_w - ITEM_HEIGHT,
480 ITEM_HEIGHT - 2*PADDING);
482 case OB_MENU_ENTRY_TYPE_SEPARATOR:
483 if (self->entry->data.separator.label != NULL) {
484 /* labeled separator */
485 XMoveResizeWindow(obt_display, self->text,
486 ob_rr_theme->paddingx, ob_rr_theme->paddingy,
487 self->area.width - 2*ob_rr_theme->paddingx,
488 ob_rr_theme->menu_title_height -
489 2*ob_rr_theme->paddingy);
490 text_a->surface.parent = item_a;
491 text_a->surface.parentx = ob_rr_theme->paddingx;
492 text_a->surface.parenty = ob_rr_theme->paddingy;
493 RrPaint(text_a, self->text,
494 self->area.width - 2*ob_rr_theme->paddingx,
495 ob_rr_theme->menu_title_height -
496 2*ob_rr_theme->paddingy);
500 /* unlabeled separator */
501 XMoveResizeWindow(obt_display, self->text, 0, 0,
503 ob_rr_theme->menu_sep_width +
504 2*ob_rr_theme->menu_sep_paddingy);
506 a_sep->surface.parent = item_a;
507 a_sep->surface.parentx = 0;
508 a_sep->surface.parenty = 0;
509 for (i = 0; i < ob_rr_theme->menu_sep_width; ++i) {
510 a_sep->texture[i].data.lineart.x1 =
511 ob_rr_theme->menu_sep_paddingx;
512 a_sep->texture[i].data.lineart.y1 =
513 ob_rr_theme->menu_sep_paddingy + i;
514 a_sep->texture[i].data.lineart.x2 =
515 self->area.width - ob_rr_theme->menu_sep_paddingx - 1;
516 a_sep->texture[i].data.lineart.y2 =
517 ob_rr_theme->menu_sep_paddingy + i;
520 RrPaint(a_sep, self->text, self->area.width,
521 ob_rr_theme->menu_sep_width +
522 2*ob_rr_theme->menu_sep_paddingy);
526 g_assert_not_reached();
529 if (((self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) ||
530 (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)) &&
531 self->entry->data.normal.icon)
535 XMoveResizeWindow(obt_display, self->icon,
536 PADDING, frame->item_margin.top,
537 ITEM_HEIGHT - frame->item_margin.top
538 - frame->item_margin.bottom,
539 ITEM_HEIGHT - frame->item_margin.top
540 - frame->item_margin.bottom);
542 clear = ob_rr_theme->a_clear_tex;
543 RrAppearanceClearTextures(clear);
544 clear->texture[0].type = RR_TEXTURE_IMAGE;
545 clear->texture[0].data.image.image =
546 self->entry->data.normal.icon;
547 clear->texture[0].data.image.alpha =
548 self->entry->data.normal.icon_alpha;
549 clear->surface.parent = item_a;
550 clear->surface.parentx = PADDING;
551 clear->surface.parenty = frame->item_margin.top;
552 RrPaint(clear, self->icon,
553 ITEM_HEIGHT - frame->item_margin.top
554 - frame->item_margin.bottom,
555 ITEM_HEIGHT - frame->item_margin.top
556 - frame->item_margin.bottom);
557 XMapWindow(obt_display, self->icon);
558 } else if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
559 self->entry->data.normal.mask)
564 XMoveResizeWindow(obt_display, self->icon,
565 PADDING, frame->item_margin.top,
566 ITEM_HEIGHT - frame->item_margin.top
567 - frame->item_margin.bottom,
568 ITEM_HEIGHT - frame->item_margin.top
569 - frame->item_margin.bottom);
571 clear = ob_rr_theme->a_clear_tex;
572 RrAppearanceClearTextures(clear);
573 clear->texture[0].type = RR_TEXTURE_MASK;
574 clear->texture[0].data.mask.mask =
575 self->entry->data.normal.mask;
577 c = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
578 !self->entry->data.normal.enabled ?
580 (self == self->frame->selected ?
581 self->entry->data.normal.mask_disabled_selected_color :
582 self->entry->data.normal.mask_disabled_color) :
584 (self == self->frame->selected ?
585 self->entry->data.normal.mask_selected_color :
586 self->entry->data.normal.mask_normal_color));
587 clear->texture[0].data.mask.color = c;
589 clear->surface.parent = item_a;
590 clear->surface.parentx = PADDING;
591 clear->surface.parenty = frame->item_margin.top;
592 RrPaint(clear, self->icon,
593 ITEM_HEIGHT - frame->item_margin.top
594 - frame->item_margin.bottom,
595 ITEM_HEIGHT - frame->item_margin.top
596 - frame->item_margin.bottom);
597 XMapWindow(obt_display, self->icon);
599 XUnmapWindow(obt_display, self->icon);
601 if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
602 RrAppearance *bullet_a;
603 XMoveResizeWindow(obt_display, self->bullet,
604 self->frame->text_x + self->frame->text_w -
605 ITEM_HEIGHT + PADDING, PADDING,
606 ITEM_HEIGHT - 2*PADDING,
607 ITEM_HEIGHT - 2*PADDING);
608 bullet_a = (self == self->frame->selected ?
609 ob_rr_theme->a_menu_bullet_selected :
610 ob_rr_theme->a_menu_bullet_normal);
611 bullet_a->surface.parent = item_a;
612 bullet_a->surface.parentx =
613 self->frame->text_x + self->frame->text_w - ITEM_HEIGHT + PADDING;
614 bullet_a->surface.parenty = PADDING;
615 RrPaint(bullet_a, self->bullet,
616 ITEM_HEIGHT - 2*PADDING,
617 ITEM_HEIGHT - 2*PADDING);
618 XMapWindow(obt_display, self->bullet);
620 XUnmapWindow(obt_display, self->bullet);
625 /*! this code is taken from the menu_frame_render. if that changes, this won't
627 static gint menu_entry_frame_get_height(ObMenuEntryFrame *self,
628 gboolean first_entry,
637 t = self->entry->type;
639 /* this is the More... entry, it's NORMAL type */
640 t = OB_MENU_ENTRY_TYPE_NORMAL;
643 case OB_MENU_ENTRY_TYPE_NORMAL:
644 case OB_MENU_ENTRY_TYPE_SUBMENU:
645 h += ob_rr_theme->menu_font_height;
647 case OB_MENU_ENTRY_TYPE_SEPARATOR:
648 if (self->entry->data.separator.label != NULL) {
649 h += ob_rr_theme->menu_title_height +
650 (ob_rr_theme->mbwidth - PADDING) * 2;
652 /* if the first entry is a labeled separator, then make its border
653 overlap with the menu's outside border */
655 h -= ob_rr_theme->mbwidth;
656 /* if the last entry is a labeled separator, then make its border
657 overlap with the menu's outside border */
659 h -= ob_rr_theme->mbwidth;
661 h += ob_rr_theme->menu_sep_width +
662 2*ob_rr_theme->menu_sep_paddingy - PADDING * 2;
670 void menu_frame_render(ObMenuFrame *self)
673 gint tw, th; /* temps */
675 gboolean has_icon = FALSE;
679 /* find text dimensions */
681 STRUT_SET(self->item_margin, 0, 0, 0, 0);
686 e = self->entries->data;
687 ob_rr_theme->a_menu_text_normal->texture[0].data.text.string = "";
688 tw = RrMinWidth(ob_rr_theme->a_menu_text_normal);
693 RrMargins(ob_rr_theme->a_menu_normal, &l, &t, &r, &b);
694 STRUT_SET(self->item_margin,
695 MAX(self->item_margin.left, l),
696 MAX(self->item_margin.top, t),
697 MAX(self->item_margin.right, r),
698 MAX(self->item_margin.bottom, b));
699 RrMargins(ob_rr_theme->a_menu_selected, &l, &t, &r, &b);
700 STRUT_SET(self->item_margin,
701 MAX(self->item_margin.left, l),
702 MAX(self->item_margin.top, t),
703 MAX(self->item_margin.right, r),
704 MAX(self->item_margin.bottom, b));
705 RrMargins(ob_rr_theme->a_menu_disabled, &l, &t, &r, &b);
706 STRUT_SET(self->item_margin,
707 MAX(self->item_margin.left, l),
708 MAX(self->item_margin.top, t),
709 MAX(self->item_margin.right, r),
710 MAX(self->item_margin.bottom, b));
711 RrMargins(ob_rr_theme->a_menu_disabled_selected, &l, &t, &r, &b);
712 STRUT_SET(self->item_margin,
713 MAX(self->item_margin.left, l),
714 MAX(self->item_margin.top, t),
715 MAX(self->item_margin.right, r),
716 MAX(self->item_margin.bottom, b));
719 /* render the entries */
721 for (it = self->entries; it; it = g_list_next(it)) {
722 RrAppearance *text_a;
725 /* if the first entry is a labeled separator, then make its border
726 overlap with the menu's outside border */
727 if (it == self->entries &&
728 e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
729 e->entry->data.separator.label)
731 h -= ob_rr_theme->mbwidth;
734 if (e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
735 e->entry->data.separator.label)
737 e->border = ob_rr_theme->mbwidth;
740 RECT_SET_POINT(e->area, 0, h+e->border);
741 XMoveWindow(obt_display, e->window,
742 e->area.x-e->border, e->area.y-e->border);
743 XSetWindowBorderWidth(obt_display, e->window, e->border);
744 XSetWindowBorder(obt_display, e->window,
745 RrColorPixel(ob_rr_theme->menu_border_color));
747 text_a = (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
748 !e->entry->data.normal.enabled ?
750 (e == self->selected ?
751 ob_rr_theme->a_menu_text_disabled_selected :
752 ob_rr_theme->a_menu_text_disabled) :
754 (e == self->selected ?
755 ob_rr_theme->a_menu_text_selected :
756 ob_rr_theme->a_menu_text_normal));
757 switch (e->entry->type) {
758 case OB_MENU_ENTRY_TYPE_NORMAL:
759 text_a->texture[0].data.text.string = e->entry->data.normal.label;
760 tw = RrMinWidth(text_a);
761 tw = MIN(tw, MAX_MENU_WIDTH);
762 th = ob_rr_theme->menu_font_height;
764 if (e->entry->data.normal.icon ||
765 e->entry->data.normal.mask)
768 case OB_MENU_ENTRY_TYPE_SUBMENU:
769 sub = e->entry->data.submenu.submenu;
770 text_a->texture[0].data.text.string = sub ? sub->title : "";
771 tw = RrMinWidth(text_a);
772 tw = MIN(tw, MAX_MENU_WIDTH);
773 th = ob_rr_theme->menu_font_height;
775 if (e->entry->data.normal.icon ||
776 e->entry->data.normal.mask)
779 tw += ITEM_HEIGHT - PADDING;
781 case OB_MENU_ENTRY_TYPE_SEPARATOR:
782 if (e->entry->data.separator.label != NULL) {
783 ob_rr_theme->a_menu_text_title->texture[0].data.text.string =
784 e->entry->data.separator.label;
785 tw = RrMinWidth(ob_rr_theme->a_menu_text_title) +
786 2*ob_rr_theme->paddingx;
787 tw = MIN(tw, MAX_MENU_WIDTH);
788 th = ob_rr_theme->menu_title_height +
789 (ob_rr_theme->mbwidth - PADDING) *2;
792 th = ob_rr_theme->menu_sep_width +
793 2*ob_rr_theme->menu_sep_paddingy - 2*PADDING;
797 g_assert_not_reached();
805 /* if the last entry is a labeled separator, then make its border
806 overlap with the menu's outside border */
807 it = g_list_last(self->entries);
808 e = it ? it->data : NULL;
809 if (e && e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
810 e->entry->data.separator.label)
812 h -= ob_rr_theme->mbwidth;
815 self->text_x = PADDING;
820 w += ITEM_HEIGHT + PADDING;
821 self->text_x += ITEM_HEIGHT + PADDING;
828 XResizeWindow(obt_display, self->window, w, h);
832 RrPaint(self->a_items, self->window, w, h);
834 for (it = self->entries; it; it = g_list_next(it))
835 menu_entry_frame_render(it->data);
837 w += ob_rr_theme->mbwidth * 2;
838 h += ob_rr_theme->mbwidth * 2;
840 RECT_SET_SIZE(self->area, w, h);
845 static void menu_frame_update(ObMenuFrame *self)
851 menu_pipe_execute(self->menu);
852 menu_find_submenus(self->menu);
854 self->selected = NULL;
856 /* start at show_from */
857 mit = g_list_nth(self->menu->entries, self->show_from);
859 /* go through the menu's and frame's entries and connect the frame entries
860 to the menu entries */
861 for (fit = self->entries; mit && fit;
862 mit = g_list_next(mit), fit = g_list_next(fit))
864 ObMenuEntryFrame *f = fit->data;
865 f->entry = mit->data;
868 /* if there are more menu entries than in the frame, add them */
870 ObMenuEntryFrame *e = menu_entry_frame_new(mit->data, self);
871 self->entries = g_list_append(self->entries, e);
872 mit = g_list_next(mit);
875 /* if there are more frame entries than menu entries then get rid of
878 GList *n = g_list_next(fit);
879 menu_entry_frame_free(fit->data);
880 self->entries = g_list_delete_link(self->entries, fit);
884 /* * make the menu fit on the screen */
886 /* calculate the height of the menu */
888 for (fit = self->entries; fit; fit = g_list_next(fit))
889 h += menu_entry_frame_get_height(fit->data,
890 fit == self->entries,
891 g_list_next(fit) == NULL);
892 /* add the border at the top and bottom */
893 h += ob_rr_theme->mbwidth * 2;
895 a = screen_physical_area_monitor(self->monitor);
899 gboolean last_entry = TRUE;
901 /* take the height of our More... entry into account */
902 h += menu_entry_frame_get_height(NULL, FALSE, TRUE);
904 /* start at the end of the entries */
905 flast = g_list_last(self->entries);
907 /* pull out all the entries from the frame that don't
908 fit on the screen, leaving at least 1 though */
909 while (h > a->height && g_list_previous(flast) != NULL) {
910 /* update the height, without this entry */
911 h -= menu_entry_frame_get_height(flast->data, FALSE, last_entry);
913 /* destroy the entry we're not displaying */
915 flast = g_list_previous(flast);
916 menu_entry_frame_free(tmp->data);
917 self->entries = g_list_delete_link(self->entries, tmp);
919 /* only the first one that we see is the last entry in the menu */
924 ObMenuEntry *more_entry;
925 ObMenuEntryFrame *more_frame;
926 /* make the More... menu entry frame which will display in this
928 if self->menu->more_menu is NULL that means that this is already
929 More... menu, so just use ourself.
931 more_entry = menu_get_more((self->menu->more_menu ?
932 self->menu->more_menu :
934 /* continue where we left off */
936 g_list_length(self->entries));
937 more_frame = menu_entry_frame_new(more_entry, self);
938 /* make it get deleted when the menu frame goes away */
939 menu_entry_unref(more_entry);
941 /* add our More... entry to the frame */
942 self->entries = g_list_append(self->entries, more_frame);
946 menu_frame_render(self);
949 static gboolean menu_frame_is_visible(ObMenuFrame *self)
951 return !!(g_list_find(menu_frame_visible, self));
954 static gboolean menu_frame_show(ObMenuFrame *self)
958 /* determine if the underlying menu is already visible */
959 for (it = menu_frame_visible; it; it = g_list_next(it)) {
960 ObMenuFrame *f = it->data;
961 if (f->menu == self->menu)
965 if (self->menu->update_func)
966 if (!self->menu->update_func(self, self->menu->data))
970 if (menu_frame_visible == NULL) {
971 /* no menus shown yet */
973 /* grab the pointer in such a way as to pass through "owner events"
974 so that we can get enter/leave notifies in the menu. */
975 if (!grab_pointer(TRUE, FALSE, OB_CURSOR_POINTER))
977 if (!grab_keyboard()) {
983 menu_frame_update(self);
985 menu_frame_visible = g_list_prepend(menu_frame_visible, self);
987 if (self->menu->show_func)
988 self->menu->show_func(self, self->menu->data);
993 gboolean menu_frame_show_topmenu(ObMenuFrame *self, gint x, gint y,
998 if (menu_frame_is_visible(self))
1000 if (!menu_frame_show(self))
1003 if (self->menu->place_func)
1004 self->menu->place_func(self, &x, &y, mouse, self->menu->data);
1006 menu_frame_place_topmenu(self, &x, &y);
1008 menu_frame_move(self, x, y);
1010 XMapWindow(obt_display, self->window);
1012 if (screen_pointer_pos(&px, &py)) {
1013 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
1014 if (e && e->frame == self)
1021 /*! Stop hiding an open submenu.
1022 @child The OnMenuFrame of the submenu to be hidden
1024 static void remove_submenu_hide_timeout(ObMenuFrame *child)
1026 if (submenu_hide_timer) g_source_remove(submenu_hide_timer);
1027 submenu_hide_timer = 0;
1030 gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent,
1031 ObMenuEntryFrame *parent_entry)
1036 if (menu_frame_is_visible(self))
1039 self->monitor = parent->monitor;
1040 self->parent = parent;
1041 self->parent_entry = parent_entry;
1043 /* set up parent's child to be us */
1044 if ((parent->child) != self) {
1046 menu_frame_hide(parent->child);
1047 parent->child = self;
1048 parent->child_entry = parent_entry;
1051 if (!menu_frame_show(self)) {
1052 parent->child = NULL;
1053 parent->child_entry = NULL;
1057 menu_frame_place_submenu(self, &x, &y);
1058 menu_frame_move_on_screen(self, x, y, &dx, &dy);
1061 /*try the other side */
1062 self->direction_right = !self->direction_right;
1063 menu_frame_place_submenu(self, &x, &y);
1064 menu_frame_move_on_screen(self, x, y, &dx, &dy);
1066 menu_frame_move(self, x + dx, y + dy);
1068 XMapWindow(obt_display, self->window);
1070 if (screen_pointer_pos(&px, &py)) {
1071 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
1072 if (e && e->frame == self)
1079 static void menu_frame_hide(ObMenuFrame *self)
1081 ObMenu *const menu = self->menu;
1082 GList *it = g_list_find(menu_frame_visible, self);
1083 gulong ignore_start;
1088 if (menu->hide_func)
1089 menu->hide_func(self, menu->data);
1092 menu_frame_hide(self->child);
1095 remove_submenu_hide_timeout(self);
1097 self->parent->child = NULL;
1098 self->parent->child_entry = NULL;
1100 self->parent = NULL;
1101 self->parent_entry = NULL;
1103 menu_frame_visible = g_list_delete_link(menu_frame_visible, it);
1105 if (menu_frame_visible == NULL) {
1106 /* last menu shown */
1111 ignore_start = event_start_ignore_all_enters();
1112 XUnmapWindow(obt_display, self->window);
1113 event_end_ignore_all_enters(ignore_start);
1115 menu_frame_free(self);
1117 if (menu->cleanup_func)
1118 menu->cleanup_func(menu, menu->data);
1121 void menu_frame_hide_all(void)
1125 if (config_submenu_show_delay) {
1126 /* remove any submenu open requests */
1127 if (submenu_show_timer) g_source_remove(submenu_show_timer);
1128 submenu_show_timer = 0;
1130 if ((it = g_list_last(menu_frame_visible)))
1131 menu_frame_hide(it->data);
1134 ObMenuFrame* menu_frame_under(gint x, gint y)
1136 ObMenuFrame *ret = NULL;
1139 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1140 ObMenuFrame *f = it->data;
1142 if (RECT_CONTAINS(f->area, x, y)) {
1150 ObMenuEntryFrame* menu_entry_frame_under(gint x, gint y)
1153 ObMenuEntryFrame *ret = NULL;
1156 if ((frame = menu_frame_under(x, y))) {
1157 x -= ob_rr_theme->mbwidth + frame->area.x;
1158 y -= ob_rr_theme->mbwidth + frame->area.y;
1160 for (it = frame->entries; it; it = g_list_next(it)) {
1161 ObMenuEntryFrame *e = it->data;
1163 if (RECT_CONTAINS(e->area, x, y)) {
1172 static gboolean submenu_show_timeout(gpointer data)
1174 g_assert(menu_frame_visible);
1175 menu_entry_frame_show_submenu((ObMenuEntryFrame*)data);
1179 static gboolean submenu_hide_timeout(gpointer data)
1181 g_assert(menu_frame_visible);
1182 menu_frame_hide((ObMenuFrame*)data);
1186 void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry,
1189 ObMenuEntryFrame *old = self->selected;
1190 ObMenuFrame *oldchild = self->child;
1191 ObMenuEntryFrame *oldchild_entry = self->child_entry;
1193 /* if the user selected a separator, ignore it and reselect what we had
1195 if (entry && entry->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR)
1199 (!old || old->entry->type != OB_MENU_ENTRY_TYPE_SUBMENU))
1202 /* if the user left this menu but we have a submenu open, move the
1203 selection back to that submenu */
1204 if (!entry && oldchild_entry)
1205 entry = oldchild_entry;
1207 if (config_submenu_show_delay) {
1208 /* remove any submenu open requests */
1209 if (submenu_show_timer) g_source_remove(submenu_show_timer);
1210 submenu_show_timer = 0;
1213 self->selected = entry;
1216 menu_entry_frame_render(old);
1218 if (oldchild_entry) {
1219 /* There is an open submenu */
1220 if (oldchild_entry == self->selected) {
1221 /* The open submenu has been reselected, so stop hiding the
1223 remove_submenu_hide_timeout(oldchild);
1225 else if (oldchild_entry == old) {
1226 /* The open submenu was selected and is no longer, so hide the
1228 if (immediate || config_submenu_hide_delay == 0)
1229 menu_frame_hide(oldchild);
1230 else if (config_submenu_hide_delay > 0) {
1231 if (submenu_hide_timer) g_source_remove(submenu_hide_timer);
1232 submenu_hide_timer =
1233 g_timeout_add_full(G_PRIORITY_DEFAULT,
1234 config_submenu_hide_delay,
1235 submenu_hide_timeout, oldchild, NULL);
1240 if (self->selected) {
1241 menu_entry_frame_render(self->selected);
1243 if (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
1244 /* only show if the submenu isn't already showing */
1245 if (oldchild_entry != self->selected) {
1246 if (immediate || config_submenu_hide_delay == 0)
1247 menu_entry_frame_show_submenu(self->selected);
1248 else if (config_submenu_hide_delay > 0) {
1249 if (submenu_show_timer)
1250 g_source_remove(submenu_show_timer);
1251 submenu_show_timer =
1252 g_timeout_add_full(G_PRIORITY_DEFAULT,
1253 config_submenu_show_delay,
1254 submenu_show_timeout,
1255 self->selected, NULL);
1258 /* hide the grandchildren of this menu. and move the cursor to
1260 else if (immediate && self->child && self->child->child) {
1261 menu_frame_hide(self->child->child);
1262 menu_frame_select(self->child, NULL, TRUE);
1268 void menu_entry_frame_show_submenu(ObMenuEntryFrame *self)
1272 if (!self->entry->data.submenu.submenu) return;
1274 f = menu_frame_new(self->entry->data.submenu.submenu,
1275 self->entry->data.submenu.show_from,
1276 self->frame->client);
1277 /* pass our direction on to our child */
1278 f->direction_right = self->frame->direction_right;
1280 if (!menu_frame_show_submenu(f, self->frame, self))
1284 void menu_entry_frame_execute(ObMenuEntryFrame *self, guint state)
1286 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1287 self->entry->data.normal.enabled)
1289 /* grab all this shizzle, cuz when the menu gets hidden, 'self'
1291 ObMenuEntry *entry = self->entry;
1292 ObMenuExecuteFunc func = self->frame->menu->execute_func;
1293 gpointer data = self->frame->menu->data;
1294 ObActionList *acts = self->entry->data.normal.actions;
1295 ObClient *client = self->frame->client;
1296 ObMenuFrame *frame = self->frame;
1297 guint mods = obt_keyboard_only_modmasks(state);
1299 /* release grabs before executing the shit */
1300 if (!(mods & ControlMask)) {
1301 event_cancel_all_key_grabs();
1306 func(entry, frame, client, state, data);
1308 action_list_run(acts, OB_USER_ACTION_MENU_SELECTION,
1309 state, -1, -1, 0, OB_FRAME_CONTEXT_NONE, client);
1313 void menu_frame_select_previous(ObMenuFrame *self)
1315 GList *it = NULL, *start;
1317 if (self->entries) {
1318 start = it = g_list_find(self->entries, self->selected);
1320 ObMenuEntryFrame *e;
1322 it = it ? g_list_previous(it) : g_list_last(self->entries);
1328 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1330 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1335 menu_frame_select(self, it ? it->data : NULL, FALSE);
1338 void menu_frame_select_next(ObMenuFrame *self)
1340 GList *it = NULL, *start;
1342 if (self->entries) {
1343 start = it = g_list_find(self->entries, self->selected);
1345 ObMenuEntryFrame *e;
1347 it = it ? g_list_next(it) : self->entries;
1353 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1355 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1360 menu_frame_select(self, it ? it->data : NULL, FALSE);
1363 void menu_frame_select_first(ObMenuFrame *self)
1367 if (self->entries) {
1368 for (it = self->entries; it; it = g_list_next(it)) {
1369 ObMenuEntryFrame *e = it->data;
1370 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1372 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1376 menu_frame_select(self, it ? it->data : NULL, FALSE);
1379 void menu_frame_select_last(ObMenuFrame *self)
1383 if (self->entries) {
1384 for (it = g_list_last(self->entries); it; it = g_list_previous(it)) {
1385 ObMenuEntryFrame *e = it->data;
1386 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1388 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1392 menu_frame_select(self, it ? it->data : NULL, FALSE);