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"
30 #include "render/theme.h"
33 #define SEPARATOR_HEIGHT 3
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
;
45 static ObMenuEntryFrame
* menu_entry_frame_new(ObMenuEntry
*entry
,
47 static void menu_entry_frame_free(ObMenuEntryFrame
*self
);
48 static void menu_frame_update(ObMenuFrame
*self
);
49 static gboolean
menu_entry_frame_submenu_timeout(gpointer data
);
50 static void menu_frame_hide(ObMenuFrame
*self
);
52 static Window
createWindow(Window parent
, gulong mask
,
53 XSetWindowAttributes
*attrib
)
55 return XCreateWindow(ob_display
, parent
, 0, 0, 1, 1, 0,
56 RrDepth(ob_rr_inst
), InputOutput
,
57 RrVisual(ob_rr_inst
), mask
, attrib
);
60 GHashTable
*menu_frame_map
;
62 void menu_frame_startup(gboolean reconfig
)
66 menu_frame_map
= g_hash_table_new(g_int_hash
, g_int_equal
);
69 void menu_frame_shutdown(gboolean reconfig
)
73 g_hash_table_destroy(menu_frame_map
);
76 ObMenuFrame
* menu_frame_new(ObMenu
*menu
, guint show_from
, ObClient
*client
)
79 XSetWindowAttributes attr
;
81 self
= g_new0(ObMenuFrame
, 1);
82 self
->type
= Window_Menu
;
84 self
->selected
= NULL
;
85 self
->client
= client
;
86 self
->direction_right
= TRUE
;
87 self
->show_from
= show_from
;
89 attr
.event_mask
= FRAME_EVENTMASK
;
90 self
->window
= createWindow(RootWindow(ob_display
, ob_screen
),
93 /* make it a popup menu type window */
94 PROP_SET32(self
->window
, net_wm_window_type
, atom
,
95 prop_atoms
.net_wm_window_type_popup_menu
);
97 XSetWindowBorderWidth(ob_display
, self
->window
, ob_rr_theme
->mbwidth
);
98 XSetWindowBorder(ob_display
, self
->window
,
99 RrColorPixel(ob_rr_theme
->menu_border_color
));
101 self
->a_items
= RrAppearanceCopy(ob_rr_theme
->a_menu
);
103 stacking_add(MENU_AS_WINDOW(self
));
108 void menu_frame_free(ObMenuFrame
*self
)
111 while (self
->entries
) {
112 menu_entry_frame_free(self
->entries
->data
);
113 self
->entries
= g_list_delete_link(self
->entries
, self
->entries
);
116 stacking_remove(MENU_AS_WINDOW(self
));
118 RrAppearanceFree(self
->a_items
);
120 XDestroyWindow(ob_display
, self
->window
);
126 static ObMenuEntryFrame
* menu_entry_frame_new(ObMenuEntry
*entry
,
129 ObMenuEntryFrame
*self
;
130 XSetWindowAttributes attr
;
132 self
= g_new0(ObMenuEntryFrame
, 1);
136 menu_entry_ref(entry
);
138 attr
.event_mask
= ENTRY_EVENTMASK
;
139 self
->window
= createWindow(self
->frame
->window
, CWEventMask
, &attr
);
140 self
->text
= createWindow(self
->window
, 0, NULL
);
141 g_hash_table_insert(menu_frame_map
, &self
->window
, self
);
142 g_hash_table_insert(menu_frame_map
, &self
->text
, self
);
143 if (entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
) {
144 self
->icon
= createWindow(self
->window
, 0, NULL
);
145 g_hash_table_insert(menu_frame_map
, &self
->icon
, self
);
147 if (entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
148 self
->bullet
= createWindow(self
->window
, 0, NULL
);
149 g_hash_table_insert(menu_frame_map
, &self
->bullet
, self
);
152 XMapWindow(ob_display
, self
->window
);
153 XMapWindow(ob_display
, self
->text
);
158 static void menu_entry_frame_free(ObMenuEntryFrame
*self
)
161 menu_entry_unref(self
->entry
);
163 XDestroyWindow(ob_display
, self
->text
);
164 XDestroyWindow(ob_display
, self
->window
);
165 g_hash_table_remove(menu_frame_map
, &self
->text
);
166 g_hash_table_remove(menu_frame_map
, &self
->window
);
167 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
) {
168 XDestroyWindow(ob_display
, self
->icon
);
169 g_hash_table_remove(menu_frame_map
, &self
->icon
);
171 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
172 XDestroyWindow(ob_display
, self
->bullet
);
173 g_hash_table_remove(menu_frame_map
, &self
->bullet
);
180 void menu_frame_move(ObMenuFrame
*self
, gint x
, gint y
)
182 RECT_SET_POINT(self
->area
, x
, y
);
183 XMoveWindow(ob_display
, self
->window
, self
->area
.x
, self
->area
.y
);
186 static void menu_frame_place_topmenu(ObMenuFrame
*self
, gint
*x
, gint
*y
)
190 if (config_menu_middle
) {
194 *y
-= self
->area
.height
/ 2;
196 /* try to the right of the cursor */
197 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
198 self
->direction_right
= TRUE
;
200 /* try to the left of the cursor */
201 myx
= *x
- self
->area
.width
;
202 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
203 self
->direction_right
= FALSE
;
206 /* if didnt fit on either side so just use what it says */
208 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
209 self
->direction_right
= TRUE
;
219 /* try to the bottom right of the cursor */
220 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
221 self
->direction_right
= TRUE
;
222 if (dx
!= 0 || dy
!= 0) {
223 /* try to the bottom left of the cursor */
224 myx
= *x
- self
->area
.width
;
226 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
227 self
->direction_right
= FALSE
;
229 if (dx
!= 0 || dy
!= 0) {
230 /* try to the top right of the cursor */
232 myy
= *y
- self
->area
.height
;
233 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
234 self
->direction_right
= TRUE
;
236 if (dx
!= 0 || dy
!= 0) {
237 /* try to the top left of the cursor */
238 myx
= *x
- self
->area
.width
;
239 myy
= *y
- self
->area
.height
;
240 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
241 self
->direction_right
= FALSE
;
243 if (dx
!= 0 || dy
!= 0) {
244 /* if didnt fit on either side so just use what it says */
247 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
248 self
->direction_right
= TRUE
;
255 static void menu_frame_place_submenu(ObMenuFrame
*self
, gint
*x
, gint
*y
)
257 gint overlapx
, overlapy
;
260 overlapx
= ob_rr_theme
->menu_overlap_x
;
261 overlapy
= ob_rr_theme
->menu_overlap_y
;
262 bwidth
= ob_rr_theme
->mbwidth
;
264 if (self
->direction_right
)
265 *x
= self
->parent
->area
.x
+ self
->parent
->area
.width
-
268 *x
= self
->parent
->area
.x
- self
->area
.width
+ overlapx
+ bwidth
;
270 *y
= self
->parent
->area
.y
+ self
->parent_entry
->area
.y
;
271 if (config_menu_middle
)
272 *y
-= (self
->area
.height
- (bwidth
* 2) - ITEM_HEIGHT
) / 2;
277 void menu_frame_move_on_screen(ObMenuFrame
*self
, gint x
, gint y
,
285 a
= screen_physical_area_monitor(self
->monitor
);
287 half
= g_list_length(self
->entries
) / 2;
288 pos
= g_list_index(self
->entries
, self
->selected
);
290 /* if in the bottom half then check this stuff first, will keep the bottom
291 edge of the menu visible */
293 *dx
= MAX(*dx
, a
->x
- x
);
294 *dy
= MAX(*dy
, a
->y
- y
);
296 *dx
= MIN(*dx
, (a
->x
+ a
->width
) - (x
+ self
->area
.width
));
297 *dy
= MIN(*dy
, (a
->y
+ a
->height
) - (y
+ self
->area
.height
));
298 /* if in the top half then check this stuff last, will keep the top
299 edge of the menu visible */
301 *dx
= MAX(*dx
, a
->x
- x
);
302 *dy
= MAX(*dy
, a
->y
- y
);
308 static void menu_entry_frame_render(ObMenuEntryFrame
*self
)
310 RrAppearance
*item_a
, *text_a
;
313 ObMenuFrame
*frame
= self
->frame
;
315 switch (self
->entry
->type
) {
316 case OB_MENU_ENTRY_TYPE_NORMAL
:
317 case OB_MENU_ENTRY_TYPE_SUBMENU
:
318 item_a
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
319 !self
->entry
->data
.normal
.enabled
?
321 (self
== self
->frame
->selected
?
322 ob_rr_theme
->a_menu_disabled_selected
:
323 ob_rr_theme
->a_menu_disabled
) :
325 (self
== self
->frame
->selected
?
326 ob_rr_theme
->a_menu_selected
:
327 ob_rr_theme
->a_menu_normal
));
330 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
331 if (self
->entry
->data
.separator
.label
) {
332 item_a
= ob_rr_theme
->a_menu_title
;
333 th
= ob_rr_theme
->menu_title_height
;
335 item_a
= ob_rr_theme
->a_menu_normal
;
336 th
= SEPARATOR_HEIGHT
+ 2*PADDING
;
340 g_assert_not_reached();
343 RECT_SET_SIZE(self
->area
, self
->frame
->inner_w
, th
);
344 XResizeWindow(ob_display
, self
->window
,
345 self
->area
.width
, self
->area
.height
);
346 item_a
->surface
.parent
= self
->frame
->a_items
;
347 item_a
->surface
.parentx
= self
->area
.x
;
348 item_a
->surface
.parenty
= self
->area
.y
;
349 RrPaint(item_a
, self
->window
, self
->area
.width
, self
->area
.height
);
351 switch (self
->entry
->type
) {
352 case OB_MENU_ENTRY_TYPE_NORMAL
:
353 text_a
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
354 !self
->entry
->data
.normal
.enabled
?
356 (self
== self
->frame
->selected
?
357 ob_rr_theme
->a_menu_text_disabled_selected
:
358 ob_rr_theme
->a_menu_text_disabled
) :
360 (self
== self
->frame
->selected
?
361 ob_rr_theme
->a_menu_text_selected
:
362 ob_rr_theme
->a_menu_text_normal
));
363 text_a
->texture
[0].data
.text
.string
= self
->entry
->data
.normal
.label
;
364 if (self
->entry
->data
.normal
.shortcut
&&
365 (self
->frame
->menu
->show_all_shortcuts
||
366 self
->entry
->data
.normal
.shortcut_always_show
||
367 self
->entry
->data
.normal
.shortcut_position
> 0))
369 text_a
->texture
[0].data
.text
.shortcut
= TRUE
;
370 text_a
->texture
[0].data
.text
.shortcut_pos
=
371 self
->entry
->data
.normal
.shortcut_position
;
373 text_a
->texture
[0].data
.text
.shortcut
= FALSE
;
375 case OB_MENU_ENTRY_TYPE_SUBMENU
:
376 text_a
= (self
== self
->frame
->selected
?
377 ob_rr_theme
->a_menu_text_selected
:
378 ob_rr_theme
->a_menu_text_normal
);
379 sub
= self
->entry
->data
.submenu
.submenu
;
380 text_a
->texture
[0].data
.text
.string
= sub
? sub
->title
: "";
381 if (sub
->shortcut
&& (self
->frame
->menu
->show_all_shortcuts
||
382 sub
->shortcut_always_show
||
383 sub
->shortcut_position
> 0))
385 text_a
->texture
[0].data
.text
.shortcut
= TRUE
;
386 text_a
->texture
[0].data
.text
.shortcut_pos
= sub
->shortcut_position
;
388 text_a
->texture
[0].data
.text
.shortcut
= FALSE
;
390 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
391 if (self
->entry
->data
.separator
.label
!= NULL
) {
392 text_a
= ob_rr_theme
->a_menu_text_title
;
393 text_a
->texture
[0].data
.text
.string
=
394 self
->entry
->data
.separator
.label
;
397 text_a
= ob_rr_theme
->a_menu_text_normal
;
401 switch (self
->entry
->type
) {
402 case OB_MENU_ENTRY_TYPE_NORMAL
:
403 XMoveResizeWindow(ob_display
, self
->text
,
404 self
->frame
->text_x
, PADDING
,
406 ITEM_HEIGHT
- 2*PADDING
);
407 text_a
->surface
.parent
= item_a
;
408 text_a
->surface
.parentx
= self
->frame
->text_x
;
409 text_a
->surface
.parenty
= PADDING
;
410 RrPaint(text_a
, self
->text
, self
->frame
->text_w
,
411 ITEM_HEIGHT
- 2*PADDING
);
413 case OB_MENU_ENTRY_TYPE_SUBMENU
:
414 XMoveResizeWindow(ob_display
, self
->text
,
415 self
->frame
->text_x
, PADDING
,
416 self
->frame
->text_w
- ITEM_HEIGHT
,
417 ITEM_HEIGHT
- 2*PADDING
);
418 text_a
->surface
.parent
= item_a
;
419 text_a
->surface
.parentx
= self
->frame
->text_x
;
420 text_a
->surface
.parenty
= PADDING
;
421 RrPaint(text_a
, self
->text
, self
->frame
->text_w
- ITEM_HEIGHT
,
422 ITEM_HEIGHT
- 2*PADDING
);
424 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
425 if (self
->entry
->data
.separator
.label
!= NULL
) {
426 /* labeled separator */
427 XMoveResizeWindow(ob_display
, self
->text
,
428 ob_rr_theme
->paddingx
, ob_rr_theme
->paddingy
,
429 self
->area
.width
- 2*ob_rr_theme
->paddingx
,
430 ob_rr_theme
->menu_title_height
-
431 2*ob_rr_theme
->paddingy
);
432 text_a
->surface
.parent
= item_a
;
433 text_a
->surface
.parentx
= ob_rr_theme
->paddingx
;
434 text_a
->surface
.parenty
= ob_rr_theme
->paddingy
;
435 RrPaint(text_a
, self
->text
,
436 self
->area
.width
- 2*ob_rr_theme
->paddingx
,
437 ob_rr_theme
->menu_title_height
-
438 2*ob_rr_theme
->paddingy
);
442 /* unlabeled separaator */
443 XMoveResizeWindow(ob_display
, self
->text
, PADDING
, PADDING
,
444 self
->area
.width
- 2*PADDING
, SEPARATOR_HEIGHT
);
446 clear
= ob_rr_theme
->a_clear_tex
;
447 RrAppearanceClearTextures(clear
);
448 clear
->texture
[0].type
= RR_TEXTURE_LINE_ART
;
449 clear
->surface
.parent
= item_a
;
450 clear
->surface
.parentx
= PADDING
;
451 clear
->surface
.parenty
= PADDING
;
452 clear
->texture
[0].data
.lineart
.color
=
453 text_a
->texture
[0].data
.text
.color
;
454 clear
->texture
[0].data
.lineart
.x1
= 2*PADDING
;
455 clear
->texture
[0].data
.lineart
.y1
= SEPARATOR_HEIGHT
/2;
456 clear
->texture
[0].data
.lineart
.x2
= self
->area
.width
- 4*PADDING
;
457 clear
->texture
[0].data
.lineart
.y2
= SEPARATOR_HEIGHT
/2;
458 RrPaint(clear
, self
->text
,
459 self
->area
.width
- 2*PADDING
, SEPARATOR_HEIGHT
);
464 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
465 self
->entry
->data
.normal
.icon
)
469 XMoveResizeWindow(ob_display
, self
->icon
,
470 PADDING
, frame
->item_margin
.top
,
471 ITEM_HEIGHT
- frame
->item_margin
.top
472 - frame
->item_margin
.bottom
,
473 ITEM_HEIGHT
- frame
->item_margin
.top
474 - frame
->item_margin
.bottom
);
476 clear
= ob_rr_theme
->a_clear_tex
;
477 RrAppearanceClearTextures(clear
);
478 clear
->texture
[0].type
= RR_TEXTURE_IMAGE
;
479 clear
->texture
[0].data
.image
.image
=
480 self
->entry
->data
.normal
.icon
;
481 clear
->texture
[0].data
.image
.alpha
=
482 self
->entry
->data
.normal
.icon_alpha
;
483 clear
->surface
.parent
= item_a
;
484 clear
->surface
.parentx
= PADDING
;
485 clear
->surface
.parenty
= frame
->item_margin
.top
;
486 RrPaint(clear
, self
->icon
,
487 ITEM_HEIGHT
- frame
->item_margin
.top
488 - frame
->item_margin
.bottom
,
489 ITEM_HEIGHT
- frame
->item_margin
.top
490 - frame
->item_margin
.bottom
);
491 XMapWindow(ob_display
, self
->icon
);
492 } else if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
493 self
->entry
->data
.normal
.mask
)
498 XMoveResizeWindow(ob_display
, self
->icon
,
499 PADDING
, frame
->item_margin
.top
,
500 ITEM_HEIGHT
- frame
->item_margin
.top
501 - frame
->item_margin
.bottom
,
502 ITEM_HEIGHT
- frame
->item_margin
.top
503 - frame
->item_margin
.bottom
);
505 clear
= ob_rr_theme
->a_clear_tex
;
506 RrAppearanceClearTextures(clear
);
507 clear
->texture
[0].type
= RR_TEXTURE_MASK
;
508 clear
->texture
[0].data
.mask
.mask
=
509 self
->entry
->data
.normal
.mask
;
511 c
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
512 !self
->entry
->data
.normal
.enabled
?
514 (self
== self
->frame
->selected
?
515 self
->entry
->data
.normal
.mask_disabled_selected_color
:
516 self
->entry
->data
.normal
.mask_disabled_color
) :
518 (self
== self
->frame
->selected
?
519 self
->entry
->data
.normal
.mask_selected_color
:
520 self
->entry
->data
.normal
.mask_normal_color
));
521 clear
->texture
[0].data
.mask
.color
= c
;
523 clear
->surface
.parent
= item_a
;
524 clear
->surface
.parentx
= PADDING
;
525 clear
->surface
.parenty
= frame
->item_margin
.top
;
526 RrPaint(clear
, self
->icon
,
527 ITEM_HEIGHT
- frame
->item_margin
.top
528 - frame
->item_margin
.bottom
,
529 ITEM_HEIGHT
- frame
->item_margin
.top
530 - frame
->item_margin
.bottom
);
531 XMapWindow(ob_display
, self
->icon
);
533 XUnmapWindow(ob_display
, self
->icon
);
535 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
536 RrAppearance
*bullet_a
;
537 XMoveResizeWindow(ob_display
, self
->bullet
,
538 self
->frame
->text_x
+ self
->frame
->text_w
-
539 ITEM_HEIGHT
+ PADDING
, PADDING
,
540 ITEM_HEIGHT
- 2*PADDING
,
541 ITEM_HEIGHT
- 2*PADDING
);
542 bullet_a
= (self
== self
->frame
->selected
?
543 ob_rr_theme
->a_menu_bullet_selected
:
544 ob_rr_theme
->a_menu_bullet_normal
);
545 bullet_a
->surface
.parent
= item_a
;
546 bullet_a
->surface
.parentx
=
547 self
->frame
->text_x
+ self
->frame
->text_w
- ITEM_HEIGHT
+ PADDING
;
548 bullet_a
->surface
.parenty
= PADDING
;
549 RrPaint(bullet_a
, self
->bullet
,
550 ITEM_HEIGHT
- 2*PADDING
,
551 ITEM_HEIGHT
- 2*PADDING
);
552 XMapWindow(ob_display
, self
->bullet
);
554 XUnmapWindow(ob_display
, self
->bullet
);
559 /*! this code is taken from the menu_frame_render. if that changes, this won't
561 static gint
menu_entry_frame_get_height(ObMenuEntryFrame
*self
,
562 gboolean first_entry
,
571 t
= self
->entry
->type
;
573 /* this is the More... entry, it's NORMAL type */
574 t
= OB_MENU_ENTRY_TYPE_NORMAL
;
577 case OB_MENU_ENTRY_TYPE_NORMAL
:
578 case OB_MENU_ENTRY_TYPE_SUBMENU
:
579 h
+= ob_rr_theme
->menu_font_height
;
581 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
582 if (self
->entry
->data
.separator
.label
!= NULL
) {
583 h
+= ob_rr_theme
->menu_title_height
+
584 (ob_rr_theme
->mbwidth
- PADDING
) * 2;
586 /* if the first entry is a labeled separator, then make its border
587 overlap with the menu's outside border */
589 h
-= ob_rr_theme
->mbwidth
;
590 /* if the last entry is a labeled separator, then make its border
591 overlap with the menu's outside border */
593 h
-= ob_rr_theme
->mbwidth
;
595 h
+= SEPARATOR_HEIGHT
;
603 void menu_frame_render(ObMenuFrame
*self
)
606 gint tw
, th
; /* temps */
608 gboolean has_icon
= FALSE
;
612 /* find text dimensions */
614 STRUT_SET(self
->item_margin
, 0, 0, 0, 0);
619 e
= self
->entries
->data
;
620 ob_rr_theme
->a_menu_text_normal
->texture
[0].data
.text
.string
= "";
621 tw
= RrMinWidth(ob_rr_theme
->a_menu_text_normal
);
626 RrMargins(ob_rr_theme
->a_menu_normal
, &l
, &t
, &r
, &b
);
627 STRUT_SET(self
->item_margin
,
628 MAX(self
->item_margin
.left
, l
),
629 MAX(self
->item_margin
.top
, t
),
630 MAX(self
->item_margin
.right
, r
),
631 MAX(self
->item_margin
.bottom
, b
));
632 RrMargins(ob_rr_theme
->a_menu_selected
, &l
, &t
, &r
, &b
);
633 STRUT_SET(self
->item_margin
,
634 MAX(self
->item_margin
.left
, l
),
635 MAX(self
->item_margin
.top
, t
),
636 MAX(self
->item_margin
.right
, r
),
637 MAX(self
->item_margin
.bottom
, b
));
638 RrMargins(ob_rr_theme
->a_menu_disabled
, &l
, &t
, &r
, &b
);
639 STRUT_SET(self
->item_margin
,
640 MAX(self
->item_margin
.left
, l
),
641 MAX(self
->item_margin
.top
, t
),
642 MAX(self
->item_margin
.right
, r
),
643 MAX(self
->item_margin
.bottom
, b
));
644 RrMargins(ob_rr_theme
->a_menu_disabled_selected
, &l
, &t
, &r
, &b
);
645 STRUT_SET(self
->item_margin
,
646 MAX(self
->item_margin
.left
, l
),
647 MAX(self
->item_margin
.top
, t
),
648 MAX(self
->item_margin
.right
, r
),
649 MAX(self
->item_margin
.bottom
, b
));
652 /* render the entries */
654 for (it
= self
->entries
; it
; it
= g_list_next(it
)) {
655 RrAppearance
*text_a
;
658 /* if the first entry is a labeled separator, then make its border
659 overlap with the menu's outside border */
660 if (it
== self
->entries
&&
661 e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
662 e
->entry
->data
.separator
.label
)
664 h
-= ob_rr_theme
->mbwidth
;
667 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
668 e
->entry
->data
.separator
.label
)
670 e
->border
= ob_rr_theme
->mbwidth
;
673 RECT_SET_POINT(e
->area
, 0, h
+e
->border
);
674 XMoveWindow(ob_display
, e
->window
,
675 e
->area
.x
-e
->border
, e
->area
.y
-e
->border
);
676 XSetWindowBorderWidth(ob_display
, e
->window
, e
->border
);
677 XSetWindowBorder(ob_display
, e
->window
,
678 RrColorPixel(ob_rr_theme
->menu_border_color
));
681 text_a
= (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
682 !e
->entry
->data
.normal
.enabled
?
684 (e
== self
->selected
?
685 ob_rr_theme
->a_menu_text_disabled_selected
:
686 ob_rr_theme
->a_menu_text_disabled
) :
688 (e
== self
->selected
?
689 ob_rr_theme
->a_menu_text_selected
:
690 ob_rr_theme
->a_menu_text_normal
));
691 switch (e
->entry
->type
) {
692 case OB_MENU_ENTRY_TYPE_NORMAL
:
693 text_a
->texture
[0].data
.text
.string
= e
->entry
->data
.normal
.label
;
694 tw
= RrMinWidth(text_a
);
695 tw
= MIN(tw
, MAX_MENU_WIDTH
);
696 th
= ob_rr_theme
->menu_font_height
;
698 if (e
->entry
->data
.normal
.icon
||
699 e
->entry
->data
.normal
.mask
)
702 case OB_MENU_ENTRY_TYPE_SUBMENU
:
703 sub
= e
->entry
->data
.submenu
.submenu
;
704 text_a
->texture
[0].data
.text
.string
= sub
? sub
->title
: "";
705 tw
= RrMinWidth(text_a
);
706 tw
= MIN(tw
, MAX_MENU_WIDTH
);
707 th
= ob_rr_theme
->menu_font_height
;
709 if (e
->entry
->data
.normal
.icon
||
710 e
->entry
->data
.normal
.mask
)
713 tw
+= ITEM_HEIGHT
- PADDING
;
715 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
716 if (e
->entry
->data
.separator
.label
!= NULL
) {
717 ob_rr_theme
->a_menu_text_title
->texture
[0].data
.text
.string
=
718 e
->entry
->data
.separator
.label
;
719 tw
= RrMinWidth(ob_rr_theme
->a_menu_text_title
) +
720 2*ob_rr_theme
->paddingx
;
721 tw
= MIN(tw
, MAX_MENU_WIDTH
);
722 th
= ob_rr_theme
->menu_title_height
+
723 (ob_rr_theme
->mbwidth
- PADDING
) *2;
726 th
= SEPARATOR_HEIGHT
;
736 /* if the last entry is a labeled separator, then make its border
737 overlap with the menu's outside border */
738 it
= g_list_last(self
->entries
);
739 e
= it
? it
->data
: NULL
;
740 if (e
&& e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
741 e
->entry
->data
.separator
.label
)
743 h
-= ob_rr_theme
->mbwidth
;
746 self
->text_x
= PADDING
;
751 w
+= ITEM_HEIGHT
+ PADDING
;
752 self
->text_x
+= ITEM_HEIGHT
+ PADDING
;
759 XResizeWindow(ob_display
, self
->window
, w
, h
);
763 RrPaint(self
->a_items
, self
->window
, w
, h
);
765 for (it
= self
->entries
; it
; it
= g_list_next(it
))
766 menu_entry_frame_render(it
->data
);
768 w
+= ob_rr_theme
->mbwidth
* 2;
769 h
+= ob_rr_theme
->mbwidth
* 2;
771 RECT_SET_SIZE(self
->area
, w
, h
);
776 static void menu_frame_update(ObMenuFrame
*self
)
782 menu_pipe_execute(self
->menu
);
783 menu_find_submenus(self
->menu
);
785 self
->selected
= NULL
;
787 /* start at show_from */
788 mit
= g_list_nth(self
->menu
->entries
, self
->show_from
);
790 /* go through the menu's and frame's entries and connect the frame entries
791 to the menu entries */
792 for (fit
= self
->entries
; mit
&& fit
;
793 mit
= g_list_next(mit
), fit
= g_list_next(fit
))
795 ObMenuEntryFrame
*f
= fit
->data
;
796 f
->entry
= mit
->data
;
799 /* if there are more menu entries than in the frame, add them */
801 ObMenuEntryFrame
*e
= menu_entry_frame_new(mit
->data
, self
);
802 self
->entries
= g_list_append(self
->entries
, e
);
803 mit
= g_list_next(mit
);
806 /* if there are more frame entries than menu entries then get rid of
809 GList
*n
= g_list_next(fit
);
810 menu_entry_frame_free(fit
->data
);
811 self
->entries
= g_list_delete_link(self
->entries
, fit
);
815 /* * make the menu fit on the screen */
817 /* calculate the height of the menu */
819 for (fit
= self
->entries
; fit
; fit
= g_list_next(fit
))
820 h
+= menu_entry_frame_get_height(fit
->data
,
821 fit
== self
->entries
,
822 g_list_next(fit
) == NULL
);
823 /* add the border at the top and bottom */
824 h
+= ob_rr_theme
->mbwidth
* 2;
826 a
= screen_physical_area_monitor(self
->monitor
);
830 gboolean last_entry
= TRUE
;
832 /* take the height of our More... entry into account */
833 h
+= menu_entry_frame_get_height(NULL
, FALSE
, TRUE
);
835 /* start at the end of the entries */
836 flast
= g_list_last(self
->entries
);
838 /* pull out all the entries from the frame that don't
839 fit on the screen, leaving at least 1 though */
840 while (h
> a
->height
&& g_list_previous(flast
) != NULL
) {
841 /* update the height, without this entry */
842 h
-= menu_entry_frame_get_height(flast
->data
, FALSE
, last_entry
);
844 /* destroy the entry we're not displaying */
846 flast
= g_list_previous(flast
);
847 menu_entry_frame_free(tmp
->data
);
848 self
->entries
= g_list_delete_link(self
->entries
, tmp
);
850 /* only the first one that we see is the last entry in the menu */
855 ObMenuEntry
*more_entry
;
856 ObMenuEntryFrame
*more_frame
;
857 /* make the More... menu entry frame which will display in this
859 if self->menu->more_menu is NULL that means that this is already
860 More... menu, so just use ourself.
862 more_entry
= menu_get_more((self
->menu
->more_menu
?
863 self
->menu
->more_menu
:
865 /* continue where we left off */
867 g_list_length(self
->entries
));
868 more_frame
= menu_entry_frame_new(more_entry
, self
);
869 /* make it get deleted when the menu frame goes away */
870 menu_entry_unref(more_entry
);
872 /* add our More... entry to the frame */
873 self
->entries
= g_list_append(self
->entries
, more_frame
);
879 menu_frame_render(self
);
882 static gboolean
menu_frame_is_visible(ObMenuFrame
*self
)
884 return !!(g_list_find(menu_frame_visible
, self
));
887 static gboolean
menu_frame_show(ObMenuFrame
*self
)
891 /* determine if the underlying menu is already visible */
892 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
893 ObMenuFrame
*f
= it
->data
;
894 if (f
->menu
== self
->menu
)
898 if (self
->menu
->update_func
)
899 if (!self
->menu
->update_func(self
, self
->menu
->data
))
903 if (menu_frame_visible
== NULL
) {
904 /* no menus shown yet */
906 /* grab the pointer in such a way as to pass through "owner events"
907 so that we can get enter/leave notifies in the menu. */
908 if (!grab_pointer(TRUE
, FALSE
, OB_CURSOR_POINTER
))
910 if (!grab_keyboard()) {
916 menu_frame_update(self
);
918 menu_frame_visible
= g_list_prepend(menu_frame_visible
, self
);
920 if (self
->menu
->show_func
)
921 self
->menu
->show_func(self
, self
->menu
->data
);
926 gboolean
menu_frame_show_topmenu(ObMenuFrame
*self
, gint x
, gint y
,
932 if (menu_frame_is_visible(self
))
934 if (!menu_frame_show(self
))
937 /* find the monitor the menu is on */
938 for (i
= 0; i
< screen_num_monitors
; ++i
) {
939 Rect
*a
= screen_physical_area_monitor(i
);
940 gboolean contains
= RECT_CONTAINS(*a
, x
, y
);
948 if (self
->menu
->place_func
)
949 self
->menu
->place_func(self
, &x
, &y
, mouse
, self
->menu
->data
);
951 menu_frame_place_topmenu(self
, &x
, &y
);
953 menu_frame_move(self
, x
, y
);
955 XMapWindow(ob_display
, self
->window
);
957 if (screen_pointer_pos(&px
, &py
)) {
958 ObMenuEntryFrame
*e
= menu_entry_frame_under(px
, py
);
959 if (e
&& e
->frame
== self
)
966 gboolean
menu_frame_show_submenu(ObMenuFrame
*self
, ObMenuFrame
*parent
,
967 ObMenuEntryFrame
*parent_entry
)
972 if (menu_frame_is_visible(self
))
975 self
->monitor
= parent
->monitor
;
976 self
->parent
= parent
;
977 self
->parent_entry
= parent_entry
;
979 /* set up parent's child to be us */
981 menu_frame_hide(parent
->child
);
982 parent
->child
= self
;
984 if (!menu_frame_show(self
))
987 menu_frame_place_submenu(self
, &x
, &y
);
988 menu_frame_move_on_screen(self
, x
, y
, &dx
, &dy
);
991 /*try the other side */
992 self
->direction_right
= !self
->direction_right
;
993 menu_frame_place_submenu(self
, &x
, &y
);
994 menu_frame_move_on_screen(self
, x
, y
, &dx
, &dy
);
996 menu_frame_move(self
, x
+ dx
, y
+ dy
);
998 XMapWindow(ob_display
, self
->window
);
1000 if (screen_pointer_pos(&px
, &py
)) {
1001 ObMenuEntryFrame
*e
= menu_entry_frame_under(px
, py
);
1002 if (e
&& e
->frame
== self
)
1009 static void menu_frame_hide(ObMenuFrame
*self
)
1011 GList
*it
= g_list_find(menu_frame_visible
, self
);
1016 if (self
->menu
->hide_func
)
1017 self
->menu
->hide_func(self
, self
->menu
->data
);
1020 menu_frame_hide(self
->child
);
1023 self
->parent
->child
= NULL
;
1024 self
->parent
= NULL
;
1025 self
->parent_entry
= NULL
;
1027 menu_frame_visible
= g_list_delete_link(menu_frame_visible
, it
);
1029 if (menu_frame_visible
== NULL
) {
1030 /* last menu shown */
1035 XUnmapWindow(ob_display
, self
->window
);
1037 menu_frame_free(self
);
1040 void menu_frame_hide_all(void)
1044 if (config_submenu_show_delay
) {
1045 /* remove any submenu open requests */
1046 ob_main_loop_timeout_remove(ob_main_loop
,
1047 menu_entry_frame_submenu_timeout
);
1049 if ((it
= g_list_last(menu_frame_visible
)))
1050 menu_frame_hide(it
->data
);
1053 void menu_frame_hide_all_client(ObClient
*client
)
1055 GList
*it
= g_list_last(menu_frame_visible
);
1057 ObMenuFrame
*f
= it
->data
;
1058 if (f
->client
== client
) {
1059 if (config_submenu_show_delay
) {
1060 /* remove any submenu open requests */
1061 ob_main_loop_timeout_remove(ob_main_loop
,
1062 menu_entry_frame_submenu_timeout
);
1070 ObMenuFrame
* menu_frame_under(gint x
, gint y
)
1072 ObMenuFrame
*ret
= NULL
;
1075 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
1076 ObMenuFrame
*f
= it
->data
;
1078 if (RECT_CONTAINS(f
->area
, x
, y
)) {
1086 ObMenuEntryFrame
* menu_entry_frame_under(gint x
, gint y
)
1089 ObMenuEntryFrame
*ret
= NULL
;
1092 if ((frame
= menu_frame_under(x
, y
))) {
1093 x
-= ob_rr_theme
->mbwidth
+ frame
->area
.x
;
1094 y
-= ob_rr_theme
->mbwidth
+ frame
->area
.y
;
1096 for (it
= frame
->entries
; it
; it
= g_list_next(it
)) {
1097 ObMenuEntryFrame
*e
= it
->data
;
1099 if (RECT_CONTAINS(e
->area
, x
, y
)) {
1108 static gboolean
menu_entry_frame_submenu_timeout(gpointer data
)
1110 g_assert(menu_frame_visible
);
1111 menu_entry_frame_show_submenu((ObMenuEntryFrame
*)data
);
1115 void menu_frame_select(ObMenuFrame
*self
, ObMenuEntryFrame
*entry
,
1118 ObMenuEntryFrame
*old
= self
->selected
;
1119 ObMenuFrame
*oldchild
= self
->child
;
1121 if (entry
&& entry
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
)
1124 if (old
== entry
) return;
1126 if (config_submenu_show_delay
) {
1127 /* remove any submenu open requests */
1128 ob_main_loop_timeout_remove(ob_main_loop
,
1129 menu_entry_frame_submenu_timeout
);
1132 self
->selected
= entry
;
1135 menu_entry_frame_render(old
);
1137 menu_frame_hide(oldchild
);
1139 if (self
->selected
) {
1140 menu_entry_frame_render(self
->selected
);
1142 if (self
->selected
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
1143 if (config_submenu_show_delay
&& !immediate
) {
1144 /* initiate a new submenu open request */
1145 ob_main_loop_timeout_add(ob_main_loop
,
1146 config_submenu_show_delay
* 1000,
1147 menu_entry_frame_submenu_timeout
,
1148 self
->selected
, g_direct_equal
,
1151 menu_entry_frame_show_submenu(self
->selected
);
1157 void menu_entry_frame_show_submenu(ObMenuEntryFrame
*self
)
1161 if (!self
->entry
->data
.submenu
.submenu
) return;
1163 f
= menu_frame_new(self
->entry
->data
.submenu
.submenu
,
1164 self
->entry
->data
.submenu
.show_from
,
1165 self
->frame
->client
);
1166 /* pass our direction on to our child */
1167 f
->direction_right
= self
->frame
->direction_right
;
1169 menu_frame_show_submenu(f
, self
->frame
, self
);
1172 void menu_entry_frame_execute(ObMenuEntryFrame
*self
, guint state
)
1174 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1175 self
->entry
->data
.normal
.enabled
)
1177 /* grab all this shizzle, cuz when the menu gets hidden, 'self'
1179 ObMenuEntry
*entry
= self
->entry
;
1180 ObMenuExecuteFunc func
= self
->frame
->menu
->execute_func
;
1181 gpointer data
= self
->frame
->menu
->data
;
1182 GSList
*acts
= self
->entry
->data
.normal
.actions
;
1183 ObClient
*client
= self
->frame
->client
;
1184 ObMenuFrame
*frame
= self
->frame
;
1186 /* release grabs before executing the shit */
1187 if (!(state
& ControlMask
)) {
1188 menu_frame_hide_all();
1193 func(entry
, frame
, client
, state
, data
);
1195 actions_run_acts(acts
, OB_USER_ACTION_MENU_SELECTION
,
1196 state
, -1, -1, 0, OB_FRAME_CONTEXT_NONE
, client
);
1200 void menu_frame_select_previous(ObMenuFrame
*self
)
1202 GList
*it
= NULL
, *start
;
1204 if (self
->entries
) {
1205 start
= it
= g_list_find(self
->entries
, self
->selected
);
1207 ObMenuEntryFrame
*e
;
1209 it
= it
? g_list_previous(it
) : g_list_last(self
->entries
);
1215 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1217 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1222 menu_frame_select(self
, it
? it
->data
: NULL
, TRUE
);
1225 void menu_frame_select_next(ObMenuFrame
*self
)
1227 GList
*it
= NULL
, *start
;
1229 if (self
->entries
) {
1230 start
= it
= g_list_find(self
->entries
, self
->selected
);
1232 ObMenuEntryFrame
*e
;
1234 it
= it
? g_list_next(it
) : self
->entries
;
1240 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1242 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1247 menu_frame_select(self
, it
? it
->data
: NULL
, TRUE
);