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"
31 #include "render/theme.h"
34 #define MAX_MENU_WIDTH 400
36 #define ITEM_HEIGHT (ob_rr_theme->menu_font_height + 2*PADDING)
38 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\
40 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
41 ButtonPressMask | ButtonReleaseMask)
43 GList *menu_frame_visible;
44 GHashTable *menu_frame_map;
46 static RrAppearance *a_sep;
48 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
50 static void menu_entry_frame_free(ObMenuEntryFrame *self);
51 static void menu_frame_update(ObMenuFrame *self);
52 static gboolean menu_entry_frame_submenu_timeout(gpointer data);
53 static void menu_frame_hide(ObMenuFrame *self);
55 static gboolean menu_entry_frame_submenu_hide_timeout(gpointer data);
57 static Window createWindow(Window parent, gulong mask,
58 XSetWindowAttributes *attrib)
60 return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
61 RrDepth(ob_rr_inst), InputOutput,
62 RrVisual(ob_rr_inst), mask, attrib);
65 void menu_frame_startup(gboolean reconfig)
69 a_sep = RrAppearanceCopy(ob_rr_theme->a_clear);
70 RrAppearanceAddTextures(a_sep, ob_rr_theme->menu_sep_width);
71 for (i = 0; i < ob_rr_theme->menu_sep_width; ++i) {
72 a_sep->texture[i].type = RR_TEXTURE_LINE_ART;
73 a_sep->texture[i].data.lineart.color =
74 ob_rr_theme->menu_sep_color;
79 menu_frame_map = g_hash_table_new(g_int_hash, g_int_equal);
82 void menu_frame_shutdown(gboolean reconfig)
84 RrAppearanceFree(a_sep);
88 g_hash_table_destroy(menu_frame_map);
91 ObMenuFrame* menu_frame_new(ObMenu *menu, guint show_from, ObClient *client)
94 XSetWindowAttributes attr;
96 self = g_new0(ObMenuFrame, 1);
97 self->type = Window_Menu;
99 self->selected = NULL;
100 self->client = client;
101 self->direction_right = TRUE;
102 self->show_from = show_from;
104 attr.event_mask = FRAME_EVENTMASK;
105 self->window = createWindow(RootWindow(ob_display, ob_screen),
108 /* make it a popup menu type window */
109 PROP_SET32(self->window, net_wm_window_type, atom,
110 prop_atoms.net_wm_window_type_popup_menu);
112 XSetWindowBorderWidth(ob_display, self->window, ob_rr_theme->mbwidth);
113 XSetWindowBorder(ob_display, self->window,
114 RrColorPixel(ob_rr_theme->menu_border_color));
116 self->a_items = RrAppearanceCopy(ob_rr_theme->a_menu);
118 stacking_add(MENU_AS_WINDOW(self));
123 void menu_frame_free(ObMenuFrame *self)
126 while (self->entries) {
127 menu_entry_frame_free(self->entries->data);
128 self->entries = g_list_delete_link(self->entries, self->entries);
131 stacking_remove(MENU_AS_WINDOW(self));
133 RrAppearanceFree(self->a_items);
135 XDestroyWindow(ob_display, self->window);
141 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
144 ObMenuEntryFrame *self;
145 XSetWindowAttributes attr;
147 self = g_new0(ObMenuEntryFrame, 1);
151 menu_entry_ref(entry);
153 attr.event_mask = ENTRY_EVENTMASK;
154 self->window = createWindow(self->frame->window, CWEventMask, &attr);
155 self->text = createWindow(self->window, 0, NULL);
156 g_hash_table_insert(menu_frame_map, &self->window, self);
157 g_hash_table_insert(menu_frame_map, &self->text, self);
158 if (entry->type == OB_MENU_ENTRY_TYPE_NORMAL) {
159 self->icon = createWindow(self->window, 0, NULL);
160 g_hash_table_insert(menu_frame_map, &self->icon, self);
162 if (entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
163 self->bullet = createWindow(self->window, 0, NULL);
164 g_hash_table_insert(menu_frame_map, &self->bullet, self);
167 XMapWindow(ob_display, self->window);
168 XMapWindow(ob_display, self->text);
173 static void menu_entry_frame_free(ObMenuEntryFrame *self)
176 menu_entry_unref(self->entry);
178 XDestroyWindow(ob_display, self->text);
179 XDestroyWindow(ob_display, self->window);
180 g_hash_table_remove(menu_frame_map, &self->text);
181 g_hash_table_remove(menu_frame_map, &self->window);
182 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL) {
183 XDestroyWindow(ob_display, self->icon);
184 g_hash_table_remove(menu_frame_map, &self->icon);
186 if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
187 XDestroyWindow(ob_display, self->bullet);
188 g_hash_table_remove(menu_frame_map, &self->bullet);
195 void menu_frame_move(ObMenuFrame *self, gint x, gint y)
197 RECT_SET_POINT(self->area, x, y);
198 self->monitor = screen_find_monitor_point(x, y);
199 XMoveWindow(ob_display, self->window, self->area.x, self->area.y);
202 static void menu_frame_place_topmenu(ObMenuFrame *self, gint *x, gint *y)
206 if (config_menu_middle) {
210 *y -= self->area.height / 2;
212 /* try to the right of the cursor */
213 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
214 self->direction_right = TRUE;
216 /* try to the left of the cursor */
217 myx = *x - self->area.width;
218 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
219 self->direction_right = FALSE;
222 /* if didnt fit on either side so just use what it says */
224 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
225 self->direction_right = TRUE;
235 /* try to the bottom right of the cursor */
236 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
237 self->direction_right = TRUE;
238 if (dx != 0 || dy != 0) {
239 /* try to the bottom left of the cursor */
240 myx = *x - self->area.width;
242 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
243 self->direction_right = FALSE;
245 if (dx != 0 || dy != 0) {
246 /* try to the top right of the cursor */
248 myy = *y - self->area.height;
249 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
250 self->direction_right = TRUE;
252 if (dx != 0 || dy != 0) {
253 /* try to the top left of the cursor */
254 myx = *x - self->area.width;
255 myy = *y - self->area.height;
256 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
257 self->direction_right = FALSE;
259 if (dx != 0 || dy != 0) {
260 /* if didnt fit on either side so just use what it says */
263 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
264 self->direction_right = TRUE;
271 static void menu_frame_place_submenu(ObMenuFrame *self, gint *x, gint *y)
273 gint overlapx, overlapy;
276 overlapx = ob_rr_theme->menu_overlap_x;
277 overlapy = ob_rr_theme->menu_overlap_y;
278 bwidth = ob_rr_theme->mbwidth;
280 if (self->direction_right)
281 *x = self->parent->area.x + self->parent->area.width -
284 *x = self->parent->area.x - self->area.width + overlapx + bwidth;
286 *y = self->parent->area.y + self->parent_entry->area.y;
287 if (config_menu_middle)
288 *y -= (self->area.height - (bwidth * 2) - ITEM_HEIGHT) / 2;
293 void menu_frame_move_on_screen(ObMenuFrame *self, gint x, gint y,
301 a = screen_physical_area_monitor(screen_find_monitor_point(x, y));
303 half = g_list_length(self->entries) / 2;
304 pos = g_list_index(self->entries, self->selected);
306 /* if in the bottom half then check this stuff first, will keep the bottom
307 edge of the menu visible */
309 *dx = MAX(*dx, a->x - x);
310 *dy = MAX(*dy, a->y - y);
312 *dx = MIN(*dx, (a->x + a->width) - (x + self->area.width));
313 *dy = MIN(*dy, (a->y + a->height) - (y + self->area.height));
314 /* if in the top half then check this stuff last, will keep the top
315 edge of the menu visible */
317 *dx = MAX(*dx, a->x - x);
318 *dy = MAX(*dy, a->y - y);
324 static void menu_entry_frame_render(ObMenuEntryFrame *self)
326 RrAppearance *item_a, *text_a;
329 ObMenuFrame *frame = self->frame;
331 switch (self->entry->type) {
332 case OB_MENU_ENTRY_TYPE_NORMAL:
333 case OB_MENU_ENTRY_TYPE_SUBMENU:
334 item_a = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
335 !self->entry->data.normal.enabled ?
337 (self == self->frame->selected ?
338 ob_rr_theme->a_menu_disabled_selected :
339 ob_rr_theme->a_menu_disabled) :
341 (self == self->frame->selected ?
342 ob_rr_theme->a_menu_selected :
343 ob_rr_theme->a_menu_normal));
346 case OB_MENU_ENTRY_TYPE_SEPARATOR:
347 if (self->entry->data.separator.label) {
348 item_a = ob_rr_theme->a_menu_title;
349 th = ob_rr_theme->menu_title_height;
351 item_a = ob_rr_theme->a_menu_normal;
352 th = ob_rr_theme->menu_sep_width +
353 2*ob_rr_theme->menu_sep_paddingy;
357 g_assert_not_reached();
360 RECT_SET_SIZE(self->area, self->frame->inner_w, th);
361 XResizeWindow(ob_display, self->window,
362 self->area.width, self->area.height);
363 item_a->surface.parent = self->frame->a_items;
364 item_a->surface.parentx = self->area.x;
365 item_a->surface.parenty = self->area.y;
366 RrPaint(item_a, self->window, self->area.width, self->area.height);
368 switch (self->entry->type) {
369 case OB_MENU_ENTRY_TYPE_NORMAL:
370 text_a = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
371 !self->entry->data.normal.enabled ?
373 (self == self->frame->selected ?
374 ob_rr_theme->a_menu_text_disabled_selected :
375 ob_rr_theme->a_menu_text_disabled) :
377 (self == self->frame->selected ?
378 ob_rr_theme->a_menu_text_selected :
379 ob_rr_theme->a_menu_text_normal));
380 text_a->texture[0].data.text.string = self->entry->data.normal.label;
381 if (self->entry->data.normal.shortcut &&
382 (self->frame->menu->show_all_shortcuts ||
383 self->entry->data.normal.shortcut_always_show ||
384 self->entry->data.normal.shortcut_position > 0))
386 text_a->texture[0].data.text.shortcut = TRUE;
387 text_a->texture[0].data.text.shortcut_pos =
388 self->entry->data.normal.shortcut_position;
390 text_a->texture[0].data.text.shortcut = FALSE;
392 case OB_MENU_ENTRY_TYPE_SUBMENU:
393 text_a = (self == self->frame->selected ?
394 ob_rr_theme->a_menu_text_selected :
395 ob_rr_theme->a_menu_text_normal);
396 sub = self->entry->data.submenu.submenu;
397 text_a->texture[0].data.text.string = sub ? sub->title : "";
398 if (sub->shortcut && (self->frame->menu->show_all_shortcuts ||
399 sub->shortcut_always_show ||
400 sub->shortcut_position > 0))
402 text_a->texture[0].data.text.shortcut = TRUE;
403 text_a->texture[0].data.text.shortcut_pos = sub->shortcut_position;
405 text_a->texture[0].data.text.shortcut = FALSE;
407 case OB_MENU_ENTRY_TYPE_SEPARATOR:
408 if (self->entry->data.separator.label != NULL) {
409 text_a = ob_rr_theme->a_menu_text_title;
410 text_a->texture[0].data.text.string =
411 self->entry->data.separator.label;
414 text_a = ob_rr_theme->a_menu_text_normal;
418 switch (self->entry->type) {
419 case OB_MENU_ENTRY_TYPE_NORMAL:
420 XMoveResizeWindow(ob_display, self->text,
421 self->frame->text_x, PADDING,
423 ITEM_HEIGHT - 2*PADDING);
424 text_a->surface.parent = item_a;
425 text_a->surface.parentx = self->frame->text_x;
426 text_a->surface.parenty = PADDING;
427 RrPaint(text_a, self->text, self->frame->text_w,
428 ITEM_HEIGHT - 2*PADDING);
430 case OB_MENU_ENTRY_TYPE_SUBMENU:
431 XMoveResizeWindow(ob_display, self->text,
432 self->frame->text_x, PADDING,
433 self->frame->text_w - ITEM_HEIGHT,
434 ITEM_HEIGHT - 2*PADDING);
435 text_a->surface.parent = item_a;
436 text_a->surface.parentx = self->frame->text_x;
437 text_a->surface.parenty = PADDING;
438 RrPaint(text_a, self->text, self->frame->text_w - ITEM_HEIGHT,
439 ITEM_HEIGHT - 2*PADDING);
441 case OB_MENU_ENTRY_TYPE_SEPARATOR:
442 if (self->entry->data.separator.label != NULL) {
443 /* labeled separator */
444 XMoveResizeWindow(ob_display, self->text,
445 ob_rr_theme->paddingx, ob_rr_theme->paddingy,
446 self->area.width - 2*ob_rr_theme->paddingx,
447 ob_rr_theme->menu_title_height -
448 2*ob_rr_theme->paddingy);
449 text_a->surface.parent = item_a;
450 text_a->surface.parentx = ob_rr_theme->paddingx;
451 text_a->surface.parenty = ob_rr_theme->paddingy;
452 RrPaint(text_a, self->text,
453 self->area.width - 2*ob_rr_theme->paddingx,
454 ob_rr_theme->menu_title_height -
455 2*ob_rr_theme->paddingy);
459 /* unlabeled separator */
460 XMoveResizeWindow(ob_display, self->text, 0, 0,
462 ob_rr_theme->menu_sep_width +
463 2*ob_rr_theme->menu_sep_paddingy);
465 a_sep->surface.parent = item_a;
466 a_sep->surface.parentx = 0;
467 a_sep->surface.parenty = 0;
468 for (i = 0; i < ob_rr_theme->menu_sep_width; ++i) {
469 a_sep->texture[i].data.lineart.x1 =
470 ob_rr_theme->menu_sep_paddingx;
471 a_sep->texture[i].data.lineart.y1 =
472 ob_rr_theme->menu_sep_paddingy + i;
473 a_sep->texture[i].data.lineart.x2 =
474 self->area.width - ob_rr_theme->menu_sep_paddingx - 1;
475 a_sep->texture[i].data.lineart.y2 =
476 ob_rr_theme->menu_sep_paddingy + i;
479 RrPaint(a_sep, self->text, self->area.width,
480 ob_rr_theme->menu_sep_width +
481 2*ob_rr_theme->menu_sep_paddingy);
486 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
487 self->entry->data.normal.icon)
491 XMoveResizeWindow(ob_display, self->icon,
492 PADDING, frame->item_margin.top,
493 ITEM_HEIGHT - frame->item_margin.top
494 - frame->item_margin.bottom,
495 ITEM_HEIGHT - frame->item_margin.top
496 - frame->item_margin.bottom);
498 clear = ob_rr_theme->a_clear_tex;
499 RrAppearanceClearTextures(clear);
500 clear->texture[0].type = RR_TEXTURE_IMAGE;
501 clear->texture[0].data.image.image =
502 self->entry->data.normal.icon;
503 clear->texture[0].data.image.alpha =
504 self->entry->data.normal.icon_alpha;
505 clear->surface.parent = item_a;
506 clear->surface.parentx = PADDING;
507 clear->surface.parenty = frame->item_margin.top;
508 RrPaint(clear, self->icon,
509 ITEM_HEIGHT - frame->item_margin.top
510 - frame->item_margin.bottom,
511 ITEM_HEIGHT - frame->item_margin.top
512 - frame->item_margin.bottom);
513 XMapWindow(ob_display, self->icon);
514 } else if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
515 self->entry->data.normal.mask)
520 XMoveResizeWindow(ob_display, self->icon,
521 PADDING, frame->item_margin.top,
522 ITEM_HEIGHT - frame->item_margin.top
523 - frame->item_margin.bottom,
524 ITEM_HEIGHT - frame->item_margin.top
525 - frame->item_margin.bottom);
527 clear = ob_rr_theme->a_clear_tex;
528 RrAppearanceClearTextures(clear);
529 clear->texture[0].type = RR_TEXTURE_MASK;
530 clear->texture[0].data.mask.mask =
531 self->entry->data.normal.mask;
533 c = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
534 !self->entry->data.normal.enabled ?
536 (self == self->frame->selected ?
537 self->entry->data.normal.mask_disabled_selected_color :
538 self->entry->data.normal.mask_disabled_color) :
540 (self == self->frame->selected ?
541 self->entry->data.normal.mask_selected_color :
542 self->entry->data.normal.mask_normal_color));
543 clear->texture[0].data.mask.color = c;
545 clear->surface.parent = item_a;
546 clear->surface.parentx = PADDING;
547 clear->surface.parenty = frame->item_margin.top;
548 RrPaint(clear, self->icon,
549 ITEM_HEIGHT - frame->item_margin.top
550 - frame->item_margin.bottom,
551 ITEM_HEIGHT - frame->item_margin.top
552 - frame->item_margin.bottom);
553 XMapWindow(ob_display, self->icon);
555 XUnmapWindow(ob_display, self->icon);
557 if (self->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
558 RrAppearance *bullet_a;
559 XMoveResizeWindow(ob_display, self->bullet,
560 self->frame->text_x + self->frame->text_w -
561 ITEM_HEIGHT + PADDING, PADDING,
562 ITEM_HEIGHT - 2*PADDING,
563 ITEM_HEIGHT - 2*PADDING);
564 bullet_a = (self == self->frame->selected ?
565 ob_rr_theme->a_menu_bullet_selected :
566 ob_rr_theme->a_menu_bullet_normal);
567 bullet_a->surface.parent = item_a;
568 bullet_a->surface.parentx =
569 self->frame->text_x + self->frame->text_w - ITEM_HEIGHT + PADDING;
570 bullet_a->surface.parenty = PADDING;
571 RrPaint(bullet_a, self->bullet,
572 ITEM_HEIGHT - 2*PADDING,
573 ITEM_HEIGHT - 2*PADDING);
574 XMapWindow(ob_display, self->bullet);
576 XUnmapWindow(ob_display, self->bullet);
581 /*! this code is taken from the menu_frame_render. if that changes, this won't
583 static gint menu_entry_frame_get_height(ObMenuEntryFrame *self,
584 gboolean first_entry,
593 t = self->entry->type;
595 /* this is the More... entry, it's NORMAL type */
596 t = OB_MENU_ENTRY_TYPE_NORMAL;
599 case OB_MENU_ENTRY_TYPE_NORMAL:
600 case OB_MENU_ENTRY_TYPE_SUBMENU:
601 h += ob_rr_theme->menu_font_height;
603 case OB_MENU_ENTRY_TYPE_SEPARATOR:
604 if (self->entry->data.separator.label != NULL) {
605 h += ob_rr_theme->menu_title_height +
606 (ob_rr_theme->mbwidth - PADDING) * 2;
608 /* if the first entry is a labeled separator, then make its border
609 overlap with the menu's outside border */
611 h -= ob_rr_theme->mbwidth;
612 /* if the last entry is a labeled separator, then make its border
613 overlap with the menu's outside border */
615 h -= ob_rr_theme->mbwidth;
617 h += ob_rr_theme->menu_sep_width +
618 2*ob_rr_theme->menu_sep_paddingy - PADDING * 2;
626 void menu_frame_render(ObMenuFrame *self)
629 gint tw, th; /* temps */
631 gboolean has_icon = FALSE;
635 /* find text dimensions */
637 STRUT_SET(self->item_margin, 0, 0, 0, 0);
642 e = self->entries->data;
643 ob_rr_theme->a_menu_text_normal->texture[0].data.text.string = "";
644 tw = RrMinWidth(ob_rr_theme->a_menu_text_normal);
649 RrMargins(ob_rr_theme->a_menu_normal, &l, &t, &r, &b);
650 STRUT_SET(self->item_margin,
651 MAX(self->item_margin.left, l),
652 MAX(self->item_margin.top, t),
653 MAX(self->item_margin.right, r),
654 MAX(self->item_margin.bottom, b));
655 RrMargins(ob_rr_theme->a_menu_selected, &l, &t, &r, &b);
656 STRUT_SET(self->item_margin,
657 MAX(self->item_margin.left, l),
658 MAX(self->item_margin.top, t),
659 MAX(self->item_margin.right, r),
660 MAX(self->item_margin.bottom, b));
661 RrMargins(ob_rr_theme->a_menu_disabled, &l, &t, &r, &b);
662 STRUT_SET(self->item_margin,
663 MAX(self->item_margin.left, l),
664 MAX(self->item_margin.top, t),
665 MAX(self->item_margin.right, r),
666 MAX(self->item_margin.bottom, b));
667 RrMargins(ob_rr_theme->a_menu_disabled_selected, &l, &t, &r, &b);
668 STRUT_SET(self->item_margin,
669 MAX(self->item_margin.left, l),
670 MAX(self->item_margin.top, t),
671 MAX(self->item_margin.right, r),
672 MAX(self->item_margin.bottom, b));
675 /* render the entries */
677 for (it = self->entries; it; it = g_list_next(it)) {
678 RrAppearance *text_a;
681 /* if the first entry is a labeled separator, then make its border
682 overlap with the menu's outside border */
683 if (it == self->entries &&
684 e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
685 e->entry->data.separator.label)
687 h -= ob_rr_theme->mbwidth;
690 if (e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
691 e->entry->data.separator.label)
693 e->border = ob_rr_theme->mbwidth;
696 RECT_SET_POINT(e->area, 0, h+e->border);
697 XMoveWindow(ob_display, e->window,
698 e->area.x-e->border, e->area.y-e->border);
699 XSetWindowBorderWidth(ob_display, e->window, e->border);
700 XSetWindowBorder(ob_display, e->window,
701 RrColorPixel(ob_rr_theme->menu_border_color));
703 text_a = (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
704 !e->entry->data.normal.enabled ?
706 (e == self->selected ?
707 ob_rr_theme->a_menu_text_disabled_selected :
708 ob_rr_theme->a_menu_text_disabled) :
710 (e == self->selected ?
711 ob_rr_theme->a_menu_text_selected :
712 ob_rr_theme->a_menu_text_normal));
713 switch (e->entry->type) {
714 case OB_MENU_ENTRY_TYPE_NORMAL:
715 text_a->texture[0].data.text.string = e->entry->data.normal.label;
716 tw = RrMinWidth(text_a);
717 tw = MIN(tw, MAX_MENU_WIDTH);
718 th = ob_rr_theme->menu_font_height;
720 if (e->entry->data.normal.icon ||
721 e->entry->data.normal.mask)
724 case OB_MENU_ENTRY_TYPE_SUBMENU:
725 sub = e->entry->data.submenu.submenu;
726 text_a->texture[0].data.text.string = sub ? sub->title : "";
727 tw = RrMinWidth(text_a);
728 tw = MIN(tw, MAX_MENU_WIDTH);
729 th = ob_rr_theme->menu_font_height;
731 if (e->entry->data.normal.icon ||
732 e->entry->data.normal.mask)
735 tw += ITEM_HEIGHT - PADDING;
737 case OB_MENU_ENTRY_TYPE_SEPARATOR:
738 if (e->entry->data.separator.label != NULL) {
739 ob_rr_theme->a_menu_text_title->texture[0].data.text.string =
740 e->entry->data.separator.label;
741 tw = RrMinWidth(ob_rr_theme->a_menu_text_title) +
742 2*ob_rr_theme->paddingx;
743 tw = MIN(tw, MAX_MENU_WIDTH);
744 th = ob_rr_theme->menu_title_height +
745 (ob_rr_theme->mbwidth - PADDING) *2;
748 th = ob_rr_theme->menu_sep_width +
749 2*ob_rr_theme->menu_sep_paddingy - 2*PADDING;
759 /* if the last entry is a labeled separator, then make its border
760 overlap with the menu's outside border */
761 it = g_list_last(self->entries);
762 e = it ? it->data : NULL;
763 if (e && e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
764 e->entry->data.separator.label)
766 h -= ob_rr_theme->mbwidth;
769 self->text_x = PADDING;
774 w += ITEM_HEIGHT + PADDING;
775 self->text_x += ITEM_HEIGHT + PADDING;
782 XResizeWindow(ob_display, self->window, w, h);
786 RrPaint(self->a_items, self->window, w, h);
788 for (it = self->entries; it; it = g_list_next(it))
789 menu_entry_frame_render(it->data);
791 w += ob_rr_theme->mbwidth * 2;
792 h += ob_rr_theme->mbwidth * 2;
794 RECT_SET_SIZE(self->area, w, h);
799 static void menu_frame_update(ObMenuFrame *self)
805 menu_pipe_execute(self->menu);
806 menu_find_submenus(self->menu);
808 self->selected = NULL;
810 /* start at show_from */
811 mit = g_list_nth(self->menu->entries, self->show_from);
813 /* go through the menu's and frame's entries and connect the frame entries
814 to the menu entries */
815 for (fit = self->entries; mit && fit;
816 mit = g_list_next(mit), fit = g_list_next(fit))
818 ObMenuEntryFrame *f = fit->data;
819 f->entry = mit->data;
822 /* if there are more menu entries than in the frame, add them */
824 ObMenuEntryFrame *e = menu_entry_frame_new(mit->data, self);
825 self->entries = g_list_append(self->entries, e);
826 mit = g_list_next(mit);
829 /* if there are more frame entries than menu entries then get rid of
832 GList *n = g_list_next(fit);
833 menu_entry_frame_free(fit->data);
834 self->entries = g_list_delete_link(self->entries, fit);
838 /* * make the menu fit on the screen */
840 /* calculate the height of the menu */
842 for (fit = self->entries; fit; fit = g_list_next(fit))
843 h += menu_entry_frame_get_height(fit->data,
844 fit == self->entries,
845 g_list_next(fit) == NULL);
846 /* add the border at the top and bottom */
847 h += ob_rr_theme->mbwidth * 2;
849 a = screen_physical_area_monitor(self->monitor);
853 gboolean last_entry = TRUE;
855 /* take the height of our More... entry into account */
856 h += menu_entry_frame_get_height(NULL, FALSE, TRUE);
858 /* start at the end of the entries */
859 flast = g_list_last(self->entries);
861 /* pull out all the entries from the frame that don't
862 fit on the screen, leaving at least 1 though */
863 while (h > a->height && g_list_previous(flast) != NULL) {
864 /* update the height, without this entry */
865 h -= menu_entry_frame_get_height(flast->data, FALSE, last_entry);
867 /* destroy the entry we're not displaying */
869 flast = g_list_previous(flast);
870 menu_entry_frame_free(tmp->data);
871 self->entries = g_list_delete_link(self->entries, tmp);
873 /* only the first one that we see is the last entry in the menu */
878 ObMenuEntry *more_entry;
879 ObMenuEntryFrame *more_frame;
880 /* make the More... menu entry frame which will display in this
882 if self->menu->more_menu is NULL that means that this is already
883 More... menu, so just use ourself.
885 more_entry = menu_get_more((self->menu->more_menu ?
886 self->menu->more_menu :
888 /* continue where we left off */
890 g_list_length(self->entries));
891 more_frame = menu_entry_frame_new(more_entry, self);
892 /* make it get deleted when the menu frame goes away */
893 menu_entry_unref(more_entry);
895 /* add our More... entry to the frame */
896 self->entries = g_list_append(self->entries, more_frame);
902 menu_frame_render(self);
905 static gboolean menu_frame_is_visible(ObMenuFrame *self)
907 return !!(g_list_find(menu_frame_visible, self));
910 static gboolean menu_frame_show(ObMenuFrame *self)
914 /* determine if the underlying menu is already visible */
915 for (it = menu_frame_visible; it; it = g_list_next(it)) {
916 ObMenuFrame *f = it->data;
917 if (f->menu == self->menu)
921 if (self->menu->update_func)
922 if (!self->menu->update_func(self, self->menu->data))
926 if (menu_frame_visible == NULL) {
927 /* no menus shown yet */
929 /* grab the pointer in such a way as to pass through "owner events"
930 so that we can get enter/leave notifies in the menu. */
931 if (!grab_pointer(TRUE, FALSE, OB_CURSOR_POINTER))
933 if (!grab_keyboard()) {
939 menu_frame_update(self);
941 menu_frame_visible = g_list_prepend(menu_frame_visible, self);
943 if (self->menu->show_func)
944 self->menu->show_func(self, self->menu->data);
949 gboolean menu_frame_show_topmenu(ObMenuFrame *self, gint x, gint y,
954 if (menu_frame_is_visible(self))
956 if (!menu_frame_show(self))
959 if (self->menu->place_func)
960 self->menu->place_func(self, &x, &y, mouse, self->menu->data);
962 menu_frame_place_topmenu(self, &x, &y);
964 menu_frame_move(self, x, y);
966 XMapWindow(ob_display, self->window);
968 if (screen_pointer_pos(&px, &py)) {
969 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
970 if (e && e->frame == self)
977 static void remove_submenu_hide_timeout(ObMenuFrame *self /* parent of submenu to hide */)
979 ob_main_loop_timeout_remove(ob_main_loop,
980 menu_entry_frame_submenu_hide_timeout);
982 self->submenu_to_hide = NULL;
985 gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent,
986 ObMenuEntryFrame *parent_entry)
991 if (menu_frame_is_visible(self))
994 self->monitor = parent->monitor;
995 self->parent = parent;
996 self->parent_entry = parent_entry;
998 remove_submenu_hide_timeout(parent);
1000 /* set up parent's child to be us */
1001 if ((parent->child) != self) {
1003 menu_frame_hide(parent->child);
1004 parent->child = self;
1007 if (!menu_frame_show(self))
1010 menu_frame_place_submenu(self, &x, &y);
1011 menu_frame_move_on_screen(self, x, y, &dx, &dy);
1014 /*try the other side */
1015 self->direction_right = !self->direction_right;
1016 menu_frame_place_submenu(self, &x, &y);
1017 menu_frame_move_on_screen(self, x, y, &dx, &dy);
1019 menu_frame_move(self, x + dx, y + dy);
1021 XMapWindow(ob_display, self->window);
1023 if (screen_pointer_pos(&px, &py)) {
1024 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
1025 if (e && e->frame == self)
1032 static void menu_frame_hide(ObMenuFrame *self)
1034 GList *it = g_list_find(menu_frame_visible, self);
1035 gulong ignore_start;
1037 remove_submenu_hide_timeout(self->parent);
1042 if (self->menu->hide_func)
1043 self->menu->hide_func(self, self->menu->data);
1046 menu_frame_hide(self->child);
1049 self->parent->child = NULL;
1050 self->parent = NULL;
1051 self->parent_entry = NULL;
1053 menu_frame_visible = g_list_delete_link(menu_frame_visible, it);
1055 if (menu_frame_visible == NULL) {
1056 /* last menu shown */
1061 ignore_start = event_start_ignore_all_enters();
1062 XUnmapWindow(ob_display, self->window);
1063 event_end_ignore_all_enters(ignore_start);
1065 menu_frame_free(self);
1068 void menu_frame_hide_all(void)
1072 if (config_submenu_show_delay) {
1073 /* remove any submenu open requests */
1074 ob_main_loop_timeout_remove(ob_main_loop,
1075 menu_entry_frame_submenu_timeout);
1077 if ((it = g_list_last(menu_frame_visible)))
1078 menu_frame_hide(it->data);
1081 void menu_frame_hide_all_client(ObClient *client)
1083 GList *it = g_list_last(menu_frame_visible);
1085 ObMenuFrame *f = it->data;
1086 if (f->client == client) {
1087 if (config_submenu_show_delay) {
1088 /* remove any submenu open requests */
1089 ob_main_loop_timeout_remove(ob_main_loop,
1090 menu_entry_frame_submenu_timeout);
1097 ObMenuFrame* menu_frame_under(gint x, gint y)
1099 ObMenuFrame *ret = NULL;
1102 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1103 ObMenuFrame *f = it->data;
1105 if (RECT_CONTAINS(f->area, x, y)) {
1113 ObMenuEntryFrame* menu_entry_frame_under(gint x, gint y)
1116 ObMenuEntryFrame *ret = NULL;
1119 if ((frame = menu_frame_under(x, y))) {
1120 x -= ob_rr_theme->mbwidth + frame->area.x;
1121 y -= ob_rr_theme->mbwidth + frame->area.y;
1123 for (it = frame->entries; it; it = g_list_next(it)) {
1124 ObMenuEntryFrame *e = it->data;
1126 if (RECT_CONTAINS(e->area, x, y)) {
1135 static gboolean menu_entry_frame_submenu_timeout(gpointer data)
1137 g_assert(menu_frame_visible);
1138 menu_entry_frame_show_submenu((ObMenuEntryFrame*)data);
1142 static gboolean menu_entry_frame_submenu_hide_timeout(gpointer data)
1144 menu_frame_hide((ObMenuFrame*)data);
1148 void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry,
1151 ObMenuEntryFrame *old = self->selected;
1152 ObMenuFrame *oldchild = self->child;
1153 ObMenuEntryFrame *temp;
1154 gboolean reselection;
1158 /* self is the last visible (sub)menu */
1159 if (self->parent && self->parent_entry != self->parent->selected) {
1161 (config_submenu_hide_delay != 0)
1162 In the parent menu corresponding entry "A" selected,
1163 this submenu ('self') shown, cursor moved in the parent
1164 menu to another entry "B", then cursor moved for the
1165 first time into this submenu.
1167 parent menu selection is "B" instead of "A",
1169 temp = self->parent->selected;
1170 self->parent->selected = self->parent_entry;
1172 menu_entry_frame_render(temp);
1173 menu_entry_frame_render(self->parent_entry);
1175 remove_submenu_hide_timeout(self->parent);
1177 else if (oldchild->child) {
1178 /* self is the (at least) grandparent of the last visible submenu */
1179 menu_frame_hide(oldchild->child);
1180 if (temp = oldchild->selected) {
1181 oldchild->selected = NULL;
1182 menu_entry_frame_render(temp);
1187 if (entry && entry->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR)
1190 if (old == entry) return;
1192 if (config_submenu_show_delay) {
1193 /* remove any submenu open requests */
1194 ob_main_loop_timeout_remove(ob_main_loop,
1195 menu_entry_frame_submenu_timeout);
1198 self->selected = entry;
1201 menu_entry_frame_render(old);
1203 reselection = FALSE;
1205 if (self->submenu_to_hide == entry) {
1207 (config_submenu_hide_delay != 0)
1208 Some entry "A" selected; corresponding submenu shown;
1209 cursor moved to another entry "B" and moved back
1210 to the entry "A", when submenu hide request added,
1211 but submenu not hided.
1214 remove_submenu_hide_timeout(self);
1216 else if (!immediate && config_submenu_hide_delay) {
1217 if (self->submenu_to_hide == NULL) {
1218 ob_main_loop_timeout_add(ob_main_loop,
1219 config_submenu_hide_delay * 1000,
1220 menu_entry_frame_submenu_hide_timeout,
1221 oldchild, g_direct_equal,
1223 self->submenu_to_hide = old;
1227 menu_frame_hide(oldchild);
1230 if (self->selected) {
1231 menu_entry_frame_render(self->selected);
1234 (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU))
1236 if (config_submenu_show_delay && !immediate) {
1237 /* initiate a new submenu open request */
1238 ob_main_loop_timeout_add(ob_main_loop,
1239 config_submenu_show_delay * 1000,
1240 menu_entry_frame_submenu_timeout,
1241 self->selected, g_direct_equal,
1244 menu_entry_frame_show_submenu(self->selected);
1250 void menu_entry_frame_show_submenu(ObMenuEntryFrame *self)
1254 if (!self->entry->data.submenu.submenu) return;
1256 f = menu_frame_new(self->entry->data.submenu.submenu,
1257 self->entry->data.submenu.show_from,
1258 self->frame->client);
1259 /* pass our direction on to our child */
1260 f->direction_right = self->frame->direction_right;
1262 menu_frame_show_submenu(f, self->frame, self);
1265 void menu_entry_frame_execute(ObMenuEntryFrame *self, guint state)
1267 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1268 self->entry->data.normal.enabled)
1270 /* grab all this shizzle, cuz when the menu gets hidden, 'self'
1272 ObMenuEntry *entry = self->entry;
1273 ObMenuExecuteFunc func = self->frame->menu->execute_func;
1274 gpointer data = self->frame->menu->data;
1275 GSList *acts = self->entry->data.normal.actions;
1276 ObClient *client = self->frame->client;
1277 ObMenuFrame *frame = self->frame;
1279 /* release grabs before executing the shit */
1280 if (!(state & ControlMask)) {
1281 menu_frame_hide_all();
1286 func(entry, frame, client, state, data);
1288 actions_run_acts(acts, OB_USER_ACTION_MENU_SELECTION,
1289 state, -1, -1, 0, OB_FRAME_CONTEXT_NONE, client);
1293 void menu_frame_select_previous(ObMenuFrame *self)
1295 GList *it = NULL, *start;
1297 if (self->entries) {
1298 start = it = g_list_find(self->entries, self->selected);
1300 ObMenuEntryFrame *e;
1302 it = it ? g_list_previous(it) : g_list_last(self->entries);
1308 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1310 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1315 menu_frame_select(self, it ? it->data : NULL, TRUE);
1318 void menu_frame_select_next(ObMenuFrame *self)
1320 GList *it = NULL, *start;
1322 if (self->entries) {
1323 start = it = g_list_find(self->entries, self->selected);
1325 ObMenuEntryFrame *e;
1327 it = it ? g_list_next(it) : self->entries;
1333 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1335 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1340 menu_frame_select(self, it ? it->data : NULL, TRUE);