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"
28 #include "render/theme.h"
31 #define SEPARATOR_HEIGHT 3
32 #define MAX_MENU_WIDTH 400
34 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\
36 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
37 ButtonPressMask | ButtonReleaseMask)
39 GList
*menu_frame_visible
;
41 static ObMenuEntryFrame
* menu_entry_frame_new(ObMenuEntry
*entry
,
43 static void menu_entry_frame_free(ObMenuEntryFrame
*self
);
44 static void menu_frame_render(ObMenuFrame
*self
);
45 static void menu_frame_update(ObMenuFrame
*self
);
46 static gboolean
menu_entry_frame_submenu_timeout(gpointer data
);
48 static Window
createWindow(Window parent
, gulong mask
,
49 XSetWindowAttributes
*attrib
)
51 return XCreateWindow(ob_display
, parent
, 0, 0, 1, 1, 0,
52 RrDepth(ob_rr_inst
), InputOutput
,
53 RrVisual(ob_rr_inst
), mask
, attrib
);
56 GHashTable
*menu_frame_map
;
58 void menu_frame_startup(gboolean reconfig
)
62 menu_frame_map
= g_hash_table_new(g_int_hash
, g_int_equal
);
65 void menu_frame_shutdown(gboolean reconfig
)
69 g_hash_table_destroy(menu_frame_map
);
72 ObMenuFrame
* menu_frame_new(ObMenu
*menu
, ObClient
*client
)
75 XSetWindowAttributes attr
;
77 self
= g_new0(ObMenuFrame
, 1);
78 self
->type
= Window_Menu
;
80 self
->selected
= NULL
;
81 self
->client
= client
;
82 self
->direction_right
= TRUE
;
84 attr
.event_mask
= FRAME_EVENTMASK
;
85 self
->window
= createWindow(RootWindow(ob_display
, ob_screen
),
88 self
->a_title
= RrAppearanceCopy(ob_rr_theme
->a_menu_title
);
89 self
->a_items
= RrAppearanceCopy(ob_rr_theme
->a_menu
);
91 stacking_add(MENU_AS_WINDOW(self
));
96 void menu_frame_free(ObMenuFrame
*self
)
99 while (self
->entries
) {
100 menu_entry_frame_free(self
->entries
->data
);
101 self
->entries
= g_list_delete_link(self
->entries
, self
->entries
);
104 stacking_remove(MENU_AS_WINDOW(self
));
106 XDestroyWindow(ob_display
, self
->window
);
108 RrAppearanceFree(self
->a_items
);
109 RrAppearanceFree(self
->a_title
);
115 static ObMenuEntryFrame
* menu_entry_frame_new(ObMenuEntry
*entry
,
118 ObMenuEntryFrame
*self
;
119 XSetWindowAttributes attr
;
121 self
= g_new0(ObMenuEntryFrame
, 1);
125 attr
.event_mask
= ENTRY_EVENTMASK
;
126 self
->window
= createWindow(self
->frame
->window
, CWEventMask
, &attr
);
127 self
->text
= createWindow(self
->window
, 0, NULL
);
128 g_hash_table_insert(menu_frame_map
, &self
->window
, self
);
129 g_hash_table_insert(menu_frame_map
, &self
->text
, self
);
130 if (entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
) {
131 self
->icon
= createWindow(self
->window
, 0, NULL
);
132 g_hash_table_insert(menu_frame_map
, &self
->icon
, self
);
134 if (entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
135 self
->bullet
= createWindow(self
->window
, 0, NULL
);
136 g_hash_table_insert(menu_frame_map
, &self
->bullet
, self
);
139 XMapWindow(ob_display
, self
->window
);
140 XMapWindow(ob_display
, self
->text
);
142 self
->a_normal
= RrAppearanceCopy(ob_rr_theme
->a_menu_normal
);
143 self
->a_selected
= RrAppearanceCopy(ob_rr_theme
->a_menu_selected
);
144 self
->a_disabled
= RrAppearanceCopy(ob_rr_theme
->a_menu_disabled
);
145 self
->a_disabled_selected
=
146 RrAppearanceCopy(ob_rr_theme
->a_menu_disabled_selected
);
148 if (entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
) {
149 self
->a_separator
= RrAppearanceCopy(ob_rr_theme
->a_clear_tex
);
150 self
->a_separator
->texture
[0].type
= RR_TEXTURE_LINE_ART
;
152 self
->a_icon
= RrAppearanceCopy(ob_rr_theme
->a_clear_tex
);
153 self
->a_icon
->texture
[0].type
= RR_TEXTURE_RGBA
;
154 self
->a_mask
= RrAppearanceCopy(ob_rr_theme
->a_clear_tex
);
155 self
->a_mask
->texture
[0].type
= RR_TEXTURE_MASK
;
156 self
->a_bullet_normal
=
157 RrAppearanceCopy(ob_rr_theme
->a_menu_bullet_normal
);
158 self
->a_bullet_selected
=
159 RrAppearanceCopy(ob_rr_theme
->a_menu_bullet_selected
);
162 self
->a_text_normal
=
163 RrAppearanceCopy(ob_rr_theme
->a_menu_text_normal
);
164 self
->a_text_selected
=
165 RrAppearanceCopy(ob_rr_theme
->a_menu_text_selected
);
166 self
->a_text_disabled
=
167 RrAppearanceCopy(ob_rr_theme
->a_menu_text_disabled
);
168 self
->a_text_disabled_selected
=
169 RrAppearanceCopy(ob_rr_theme
->a_menu_text_disabled_selected
);
171 RrAppearanceCopy(ob_rr_theme
->a_menu_text_title
);
176 static void menu_entry_frame_free(ObMenuEntryFrame
*self
)
179 XDestroyWindow(ob_display
, self
->text
);
180 XDestroyWindow(ob_display
, self
->window
);
181 g_hash_table_remove(menu_frame_map
, &self
->text
);
182 g_hash_table_remove(menu_frame_map
, &self
->window
);
183 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
) {
184 XDestroyWindow(ob_display
, self
->icon
);
185 g_hash_table_remove(menu_frame_map
, &self
->icon
);
187 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
188 XDestroyWindow(ob_display
, self
->bullet
);
189 g_hash_table_remove(menu_frame_map
, &self
->bullet
);
192 RrAppearanceFree(self
->a_normal
);
193 RrAppearanceFree(self
->a_selected
);
194 RrAppearanceFree(self
->a_disabled
);
195 RrAppearanceFree(self
->a_disabled_selected
);
197 RrAppearanceFree(self
->a_separator
);
198 RrAppearanceFree(self
->a_icon
);
199 RrAppearanceFree(self
->a_mask
);
200 RrAppearanceFree(self
->a_text_normal
);
201 RrAppearanceFree(self
->a_text_selected
);
202 RrAppearanceFree(self
->a_text_disabled
);
203 RrAppearanceFree(self
->a_text_disabled_selected
);
204 RrAppearanceFree(self
->a_text_title
);
205 RrAppearanceFree(self
->a_bullet_normal
);
206 RrAppearanceFree(self
->a_bullet_selected
);
212 void menu_frame_move(ObMenuFrame
*self
, gint x
, gint y
)
214 RECT_SET_POINT(self
->area
, x
, y
);
215 XMoveWindow(ob_display
, self
->window
, self
->area
.x
, self
->area
.y
);
218 static void menu_frame_place_topmenu(ObMenuFrame
*self
, gint
*x
, gint
*y
)
222 if (config_menu_middle
) {
226 *y
-= self
->area
.height
/ 2;
228 /* try to the right of the cursor */
229 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
230 self
->direction_right
= TRUE
;
232 /* try to the left of the cursor */
233 myx
= *x
- self
->area
.width
;
234 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
235 self
->direction_right
= FALSE
;
238 /* if didnt fit on either side so just use what it says */
240 menu_frame_move_on_screen(self
, myx
, *y
, &dx
, &dy
);
241 self
->direction_right
= TRUE
;
251 /* try to the bottom right of the cursor */
252 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
253 self
->direction_right
= TRUE
;
254 if (dx
!= 0 || dy
!= 0) {
255 /* try to the bottom left of the cursor */
256 myx
= *x
- self
->area
.width
;
258 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
259 self
->direction_right
= FALSE
;
261 if (dx
!= 0 || dy
!= 0) {
262 /* try to the top right of the cursor */
264 myy
= *y
- self
->area
.height
;
265 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
266 self
->direction_right
= TRUE
;
268 if (dx
!= 0 || dy
!= 0) {
269 /* try to the top left of the cursor */
270 myx
= *x
- self
->area
.width
;
271 myy
= *y
- self
->area
.height
;
272 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
273 self
->direction_right
= FALSE
;
275 if (dx
!= 0 || dy
!= 0) {
276 /* if didnt fit on either side so just use what it says */
279 menu_frame_move_on_screen(self
, myx
, myy
, &dx
, &dy
);
280 self
->direction_right
= TRUE
;
287 static void menu_frame_place_submenu(ObMenuFrame
*self
, gint
*x
, gint
*y
)
292 overlap
= ob_rr_theme
->menu_overlap
;
293 bwidth
= ob_rr_theme
->mbwidth
;
295 if (self
->direction_right
)
296 *x
= self
->parent
->area
.x
+ self
->parent
->area
.width
-
299 *x
= self
->parent
->area
.x
- self
->area
.width
+ overlap
+ bwidth
;
301 *y
= self
->parent
->area
.y
+ self
->parent_entry
->area
.y
;
302 if (config_menu_middle
)
303 *y
-= (self
->area
.height
- (bwidth
* 2) - self
->item_h
) / 2;
308 void menu_frame_move_on_screen(ObMenuFrame
*self
, gint x
, gint y
,
316 a
= screen_physical_area_monitor(self
->monitor
);
318 half
= g_list_length(self
->entries
) / 2;
319 pos
= g_list_index(self
->entries
, self
->selected
);
321 /* if in the bottom half then check this stuff first, will keep the bottom
322 edge of the menu visible */
324 *dx
= MAX(*dx
, a
->x
- x
);
325 *dy
= MAX(*dy
, a
->y
- y
);
327 *dx
= MIN(*dx
, (a
->x
+ a
->width
) - (x
+ self
->area
.width
));
328 *dy
= MIN(*dy
, (a
->y
+ a
->height
) - (y
+ self
->area
.height
));
329 /* if in the top half then check this stuff last, will keep the top
330 edge of the menu visible */
332 *dx
= MAX(*dx
, a
->x
- x
);
333 *dy
= MAX(*dy
, a
->y
- y
);
337 static void menu_entry_frame_render(ObMenuEntryFrame
*self
)
339 RrAppearance
*item_a
, *text_a
;
342 ObMenuFrame
*frame
= self
->frame
;
344 switch (self
->entry
->type
) {
345 case OB_MENU_ENTRY_TYPE_NORMAL
:
346 case OB_MENU_ENTRY_TYPE_SUBMENU
:
347 item_a
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
348 !self
->entry
->data
.normal
.enabled
?
350 (self
== self
->frame
->selected
?
351 self
->a_disabled_selected
: self
->a_disabled
) :
353 (self
== self
->frame
->selected
?
354 self
->a_selected
: self
->a_normal
));
355 th
= self
->frame
->item_h
;
357 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
358 if (self
->entry
->data
.separator
.label
) {
359 item_a
= self
->frame
->a_title
;
360 th
= ob_rr_theme
->menu_title_height
;
362 item_a
= self
->a_normal
;
363 th
= SEPARATOR_HEIGHT
+ 2*PADDING
;
367 g_assert_not_reached();
369 RECT_SET_SIZE(self
->area
, self
->frame
->inner_w
, th
);
370 XResizeWindow(ob_display
, self
->window
,
371 self
->area
.width
, self
->area
.height
);
372 item_a
->surface
.parent
= self
->frame
->a_items
;
373 item_a
->surface
.parentx
= self
->area
.x
;
374 item_a
->surface
.parenty
= self
->area
.y
;
375 RrPaint(item_a
, self
->window
, self
->area
.width
, self
->area
.height
);
377 switch (self
->entry
->type
) {
378 case OB_MENU_ENTRY_TYPE_NORMAL
:
379 text_a
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
380 !self
->entry
->data
.normal
.enabled
?
382 (self
== self
->frame
->selected
?
383 self
->a_text_disabled_selected
: self
->a_text_disabled
) :
385 (self
== self
->frame
->selected
?
386 self
->a_text_selected
: self
->a_text_normal
));
387 text_a
->texture
[0].data
.text
.string
= self
->entry
->data
.normal
.label
;
388 if (self
->entry
->data
.normal
.shortcut
&&
389 (self
->frame
->menu
->show_all_shortcuts
||
390 self
->entry
->data
.normal
.shortcut_position
> 0))
392 text_a
->texture
[0].data
.text
.shortcut
= TRUE
;
393 text_a
->texture
[0].data
.text
.shortcut_pos
=
394 self
->entry
->data
.normal
.shortcut_position
;
396 text_a
->texture
[0].data
.text
.shortcut
= FALSE
;
398 case OB_MENU_ENTRY_TYPE_SUBMENU
:
399 text_a
= (self
== self
->frame
->selected
?
400 self
->a_text_selected
:
401 self
->a_text_normal
);
402 sub
= self
->entry
->data
.submenu
.submenu
;
403 text_a
->texture
[0].data
.text
.string
= sub
? sub
->title
: "";
404 if (sub
->shortcut
&& (self
->frame
->menu
->show_all_shortcuts
||
405 sub
->shortcut_position
> 0))
407 text_a
->texture
[0].data
.text
.shortcut
= TRUE
;
408 text_a
->texture
[0].data
.text
.shortcut_pos
= sub
->shortcut_position
;
410 text_a
->texture
[0].data
.text
.shortcut
= FALSE
;
412 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
413 if (self
->entry
->data
.separator
.label
!= NULL
)
414 text_a
= self
->a_text_title
;
416 text_a
= self
->a_text_normal
;
420 switch (self
->entry
->type
) {
421 case OB_MENU_ENTRY_TYPE_NORMAL
:
422 XMoveResizeWindow(ob_display
, self
->text
,
423 self
->frame
->text_x
, PADDING
,
425 self
->frame
->item_h
- 2*PADDING
);
426 text_a
->surface
.parent
= item_a
;
427 text_a
->surface
.parentx
= self
->frame
->text_x
;
428 text_a
->surface
.parenty
= PADDING
;
429 RrPaint(text_a
, self
->text
, self
->frame
->text_w
,
430 self
->frame
->item_h
- 2*PADDING
);
432 case OB_MENU_ENTRY_TYPE_SUBMENU
:
433 XMoveResizeWindow(ob_display
, self
->text
,
434 self
->frame
->text_x
, PADDING
,
435 self
->frame
->text_w
- self
->frame
->item_h
,
436 self
->frame
->item_h
- 2*PADDING
);
437 text_a
->surface
.parent
= item_a
;
438 text_a
->surface
.parentx
= self
->frame
->text_x
;
439 text_a
->surface
.parenty
= PADDING
;
440 RrPaint(text_a
, self
->text
, self
->frame
->text_w
- self
->frame
->item_h
,
441 self
->frame
->item_h
- 2*PADDING
);
443 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
444 if (self
->entry
->data
.separator
.label
!= NULL
) {
445 /* labeled separator */
446 XMoveResizeWindow(ob_display
, self
->text
,
447 ob_rr_theme
->paddingx
, ob_rr_theme
->paddingy
,
448 self
->area
.width
- 2*ob_rr_theme
->paddingx
,
449 ob_rr_theme
->menu_title_height
-
450 2*ob_rr_theme
->paddingy
);
451 text_a
->surface
.parent
= item_a
;
452 text_a
->surface
.parentx
= ob_rr_theme
->paddingx
;
453 text_a
->surface
.parenty
= ob_rr_theme
->paddingy
;
454 RrPaint(text_a
, self
->text
,
455 self
->area
.width
- 2*ob_rr_theme
->paddingx
,
456 ob_rr_theme
->menu_title_height
-
457 2*ob_rr_theme
->paddingy
);
459 /* unlabeled separaator */
460 XMoveResizeWindow(ob_display
, self
->text
, PADDING
, PADDING
,
461 self
->area
.width
- 2*PADDING
, SEPARATOR_HEIGHT
);
462 self
->a_separator
->surface
.parent
= item_a
;
463 self
->a_separator
->surface
.parentx
= PADDING
;
464 self
->a_separator
->surface
.parenty
= PADDING
;
465 self
->a_separator
->texture
[0].data
.lineart
.color
=
466 text_a
->texture
[0].data
.text
.color
;
467 self
->a_separator
->texture
[0].data
.lineart
.x1
= 2*PADDING
;
468 self
->a_separator
->texture
[0].data
.lineart
.y1
= SEPARATOR_HEIGHT
/2;
469 self
->a_separator
->texture
[0].data
.lineart
.x2
=
470 self
->area
.width
- 4*PADDING
;
471 self
->a_separator
->texture
[0].data
.lineart
.y2
= SEPARATOR_HEIGHT
/2;
472 RrPaint(self
->a_separator
, self
->text
,
473 self
->area
.width
- 2*PADDING
, SEPARATOR_HEIGHT
);
478 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
479 self
->entry
->data
.normal
.icon_data
)
481 XMoveResizeWindow(ob_display
, self
->icon
,
482 PADDING
, frame
->item_margin
.top
,
483 self
->frame
->item_h
- frame
->item_margin
.top
484 - frame
->item_margin
.bottom
,
485 self
->frame
->item_h
- frame
->item_margin
.top
486 - frame
->item_margin
.bottom
);
487 self
->a_icon
->texture
[0].data
.rgba
.width
=
488 self
->entry
->data
.normal
.icon_width
;
489 self
->a_icon
->texture
[0].data
.rgba
.height
=
490 self
->entry
->data
.normal
.icon_height
;
491 self
->a_icon
->texture
[0].data
.rgba
.data
=
492 self
->entry
->data
.normal
.icon_data
;
493 self
->a_icon
->surface
.parent
= item_a
;
494 self
->a_icon
->surface
.parentx
= PADDING
;
495 self
->a_icon
->surface
.parenty
= frame
->item_margin
.top
;
496 RrPaint(self
->a_icon
, self
->icon
,
497 self
->frame
->item_h
- frame
->item_margin
.top
498 - frame
->item_margin
.bottom
,
499 self
->frame
->item_h
- frame
->item_margin
.top
500 - frame
->item_margin
.bottom
);
501 XMapWindow(ob_display
, self
->icon
);
502 } else if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
503 self
->entry
->data
.normal
.mask
)
507 XMoveResizeWindow(ob_display
, self
->icon
,
508 PADDING
, frame
->item_margin
.top
,
509 self
->frame
->item_h
- frame
->item_margin
.top
510 - frame
->item_margin
.bottom
,
511 self
->frame
->item_h
- frame
->item_margin
.top
512 - frame
->item_margin
.bottom
);
513 self
->a_mask
->texture
[0].data
.mask
.mask
=
514 self
->entry
->data
.normal
.mask
;
516 c
= (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
517 !self
->entry
->data
.normal
.enabled
?
519 (self
== self
->frame
->selected
?
520 self
->entry
->data
.normal
.mask_disabled_selected_color
:
521 self
->entry
->data
.normal
.mask_disabled_color
) :
523 (self
== self
->frame
->selected
?
524 self
->entry
->data
.normal
.mask_selected_color
:
525 self
->entry
->data
.normal
.mask_normal_color
));
526 self
->a_mask
->texture
[0].data
.mask
.color
= c
;
528 self
->a_mask
->surface
.parent
= item_a
;
529 self
->a_mask
->surface
.parentx
= PADDING
;
530 self
->a_mask
->surface
.parenty
= frame
->item_margin
.top
;
531 RrPaint(self
->a_mask
, self
->icon
,
532 self
->frame
->item_h
- frame
->item_margin
.top
533 - frame
->item_margin
.bottom
,
534 self
->frame
->item_h
- frame
->item_margin
.top
535 - frame
->item_margin
.bottom
);
536 XMapWindow(ob_display
, self
->icon
);
538 XUnmapWindow(ob_display
, self
->icon
);
540 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
541 RrAppearance
*bullet_a
;
542 XMoveResizeWindow(ob_display
, self
->bullet
,
543 self
->frame
->text_x
+ self
->frame
->text_w
544 - self
->frame
->item_h
+ PADDING
, PADDING
,
545 self
->frame
->item_h
- 2*PADDING
,
546 self
->frame
->item_h
- 2*PADDING
);
547 bullet_a
= (self
== self
->frame
->selected
?
548 self
->a_bullet_selected
:
549 self
->a_bullet_normal
);
550 bullet_a
->surface
.parent
= item_a
;
551 bullet_a
->surface
.parentx
=
552 self
->frame
->text_x
+ self
->frame
->text_w
- self
->frame
->item_h
554 bullet_a
->surface
.parenty
= PADDING
;
555 RrPaint(bullet_a
, self
->bullet
,
556 self
->frame
->item_h
- 2*PADDING
,
557 self
->frame
->item_h
- 2*PADDING
);
558 XMapWindow(ob_display
, self
->bullet
);
560 XUnmapWindow(ob_display
, self
->bullet
);
565 static void menu_frame_render(ObMenuFrame
*self
)
568 gint tw
, th
; /* temps */
570 gboolean has_icon
= FALSE
;
574 XSetWindowBorderWidth(ob_display
, self
->window
, ob_rr_theme
->mbwidth
);
575 XSetWindowBorder(ob_display
, self
->window
,
576 RrColorPixel(ob_rr_theme
->menu_b_color
));
578 /* find text dimensions */
580 STRUT_SET(self
->item_margin
, 0, 0, 0, 0);
583 ObMenuEntryFrame
*e
= self
->entries
->data
;
586 e
->a_text_normal
->texture
[0].data
.text
.string
= "";
587 RrMinSize(e
->a_text_normal
, &tw
, &th
);
592 RrMargins(e
->a_normal
, &l
, &t
, &r
, &b
);
593 STRUT_SET(self
->item_margin
,
594 MAX(self
->item_margin
.left
, l
),
595 MAX(self
->item_margin
.top
, t
),
596 MAX(self
->item_margin
.right
, r
),
597 MAX(self
->item_margin
.bottom
, b
));
598 RrMargins(e
->a_selected
, &l
, &t
, &r
, &b
);
599 STRUT_SET(self
->item_margin
,
600 MAX(self
->item_margin
.left
, l
),
601 MAX(self
->item_margin
.top
, t
),
602 MAX(self
->item_margin
.right
, r
),
603 MAX(self
->item_margin
.bottom
, b
));
604 RrMargins(e
->a_disabled
, &l
, &t
, &r
, &b
);
605 STRUT_SET(self
->item_margin
,
606 MAX(self
->item_margin
.left
, l
),
607 MAX(self
->item_margin
.top
, t
),
608 MAX(self
->item_margin
.right
, r
),
609 MAX(self
->item_margin
.bottom
, b
));
610 RrMargins(e
->a_disabled_selected
, &l
, &t
, &r
, &b
);
611 STRUT_SET(self
->item_margin
,
612 MAX(self
->item_margin
.left
, l
),
613 MAX(self
->item_margin
.top
, t
),
614 MAX(self
->item_margin
.right
, r
),
615 MAX(self
->item_margin
.bottom
, b
));
619 /* render the entries */
621 for (it
= self
->entries
; it
; it
= g_list_next(it
)) {
622 RrAppearance
*text_a
;
625 /* if the first entry is a labeled separator, then make its border
626 overlap with the menu's outside border */
627 if (it
== self
->entries
&&
628 e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
629 e
->entry
->data
.separator
.label
)
631 h
-= ob_rr_theme
->mbwidth
;
634 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
635 e
->entry
->data
.separator
.label
)
637 e
->border
= ob_rr_theme
->mbwidth
;
640 RECT_SET_POINT(e
->area
, 0, h
+e
->border
);
641 XMoveWindow(ob_display
, e
->window
, e
->area
.x
-e
->border
, e
->area
.y
-e
->border
);
642 XSetWindowBorderWidth(ob_display
, e
->window
, e
->border
);
643 XSetWindowBorder(ob_display
, e
->window
,
644 RrColorPixel(ob_rr_theme
->menu_b_color
));
647 text_a
= (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
648 !e
->entry
->data
.normal
.enabled
?
650 (e
== self
->selected
?
651 e
->a_text_disabled_selected
: e
->a_text_disabled
) :
653 (e
== self
->selected
?
654 e
->a_text_selected
: e
->a_text_normal
));
655 switch (e
->entry
->type
) {
656 case OB_MENU_ENTRY_TYPE_NORMAL
:
657 text_a
->texture
[0].data
.text
.string
= e
->entry
->data
.normal
.label
;
658 RrMinSize(text_a
, &tw
, &th
);
659 tw
= MIN(tw
, MAX_MENU_WIDTH
);
661 if (e
->entry
->data
.normal
.icon_data
||
662 e
->entry
->data
.normal
.mask
)
665 case OB_MENU_ENTRY_TYPE_SUBMENU
:
666 sub
= e
->entry
->data
.submenu
.submenu
;
667 text_a
->texture
[0].data
.text
.string
= sub
? sub
->title
: "";
668 RrMinSize(text_a
, &tw
, &th
);
669 tw
= MIN(tw
, MAX_MENU_WIDTH
);
671 if (e
->entry
->data
.normal
.icon_data
||
672 e
->entry
->data
.normal
.mask
)
675 tw
+= self
->item_h
- PADDING
;
677 case OB_MENU_ENTRY_TYPE_SEPARATOR
:
678 if (e
->entry
->data
.separator
.label
!= NULL
) {
679 e
->a_text_title
->texture
[0].data
.text
.string
=
680 e
->entry
->data
.separator
.label
;
681 RrMinSize(e
->a_text_title
, &tw
, &th
);
682 tw
= MIN(tw
, MAX_MENU_WIDTH
);
683 th
= ob_rr_theme
->menu_title_height
+
684 (ob_rr_theme
->mbwidth
- PADDING
) *2;
687 th
= SEPARATOR_HEIGHT
;
697 /* if the last entry is a labeled separator, then make its border
698 overlap with the menu's outside border */
699 it
= g_list_last(self
->entries
);
700 e
= it
? it
->data
: NULL
;
701 if (e
&& e
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
&&
702 e
->entry
->data
.separator
.label
)
704 h
-= ob_rr_theme
->mbwidth
;
707 self
->text_x
= PADDING
;
712 w
+= self
->item_h
+ PADDING
;
713 self
->text_x
+= self
->item_h
+ PADDING
;
720 XResizeWindow(ob_display
, self
->window
, w
, h
);
724 RrPaint(self
->a_items
, self
->window
, w
, h
);
726 for (it
= self
->entries
; it
; it
= g_list_next(it
))
727 menu_entry_frame_render(it
->data
);
729 w
+= ob_rr_theme
->mbwidth
* 2;
730 h
+= ob_rr_theme
->mbwidth
* 2;
732 RECT_SET_SIZE(self
->area
, w
, h
);
737 static void menu_frame_update(ObMenuFrame
*self
)
741 menu_pipe_execute(self
->menu
);
742 menu_find_submenus(self
->menu
);
744 self
->selected
= NULL
;
746 for (mit
= self
->menu
->entries
, fit
= self
->entries
; mit
&& fit
;
747 mit
= g_list_next(mit
), fit
= g_list_next(fit
))
749 ObMenuEntryFrame
*f
= fit
->data
;
750 f
->entry
= mit
->data
;
754 ObMenuEntryFrame
*e
= menu_entry_frame_new(mit
->data
, self
);
755 self
->entries
= g_list_append(self
->entries
, e
);
756 mit
= g_list_next(mit
);
760 GList
*n
= g_list_next(fit
);
761 menu_entry_frame_free(fit
->data
);
762 self
->entries
= g_list_delete_link(self
->entries
, fit
);
766 menu_frame_render(self
);
769 static gboolean
menu_frame_is_visible(ObMenuFrame
*self
)
771 return !!(g_list_find(menu_frame_visible
, self
));
774 static gboolean
menu_frame_show(ObMenuFrame
*self
)
778 /* determine if the underlying menu is already visible */
779 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
780 ObMenuFrame
*f
= it
->data
;
781 if (f
->menu
== self
->menu
)
785 if (self
->menu
->update_func
)
786 if (!self
->menu
->update_func(self
, self
->menu
->data
))
790 if (menu_frame_visible
== NULL
) {
791 /* no menus shown yet */
792 if (!grab_pointer(TRUE
, TRUE
, OB_CURSOR_POINTER
))
794 if (!grab_keyboard(TRUE
)) {
795 grab_pointer(FALSE
, TRUE
, OB_CURSOR_POINTER
);
800 menu_frame_update(self
);
802 menu_frame_visible
= g_list_prepend(menu_frame_visible
, self
);
807 gboolean
menu_frame_show_topmenu(ObMenuFrame
*self
, gint x
, gint y
,
812 if (menu_frame_is_visible(self
))
814 if (!menu_frame_show(self
))
817 /* find the monitor the menu is on */
818 for (i
= 0; i
< screen_num_monitors
; ++i
) {
819 Rect
*a
= screen_physical_area_monitor(i
);
820 if (RECT_CONTAINS(*a
, x
, y
)) {
826 if (self
->menu
->place_func
)
827 self
->menu
->place_func(self
, &x
, &y
, button
, self
->menu
->data
);
829 menu_frame_place_topmenu(self
, &x
, &y
);
831 menu_frame_move(self
, x
, y
);
833 XMapWindow(ob_display
, self
->window
);
838 gboolean
menu_frame_show_submenu(ObMenuFrame
*self
, ObMenuFrame
*parent
,
839 ObMenuEntryFrame
*parent_entry
)
844 if (menu_frame_is_visible(self
))
847 self
->monitor
= parent
->monitor
;
848 self
->parent
= parent
;
849 self
->parent_entry
= parent_entry
;
851 /* set up parent's child to be us */
853 menu_frame_hide(parent
->child
);
854 parent
->child
= self
;
856 if (!menu_frame_show(self
))
859 menu_frame_place_submenu(self
, &x
, &y
);
860 menu_frame_move_on_screen(self
, x
, y
, &dx
, &dy
);
863 /*try the other side */
864 self
->direction_right
= !self
->direction_right
;
865 menu_frame_place_submenu(self
, &x
, &y
);
866 menu_frame_move_on_screen(self
, x
, y
, &dx
, &dy
);
868 menu_frame_move(self
, x
+ dx
, y
+ dy
);
870 XMapWindow(ob_display
, self
->window
);
872 if (screen_pointer_pos(&dx
, &dy
) && (e
= menu_entry_frame_under(dx
, dy
)) &&
879 void menu_frame_hide(ObMenuFrame
*self
)
881 GList
*it
= g_list_find(menu_frame_visible
, self
);
887 menu_frame_hide(self
->child
);
890 self
->parent
->child
= NULL
;
892 self
->parent_entry
= NULL
;
894 menu_frame_visible
= g_list_delete_link(menu_frame_visible
, it
);
896 if (menu_frame_visible
== NULL
) {
897 /* last menu shown */
898 grab_pointer(FALSE
, TRUE
, OB_CURSOR_NONE
);
899 grab_keyboard(FALSE
);
902 XUnmapWindow(ob_display
, self
->window
);
904 menu_frame_free(self
);
907 void menu_frame_hide_all()
911 if (config_submenu_show_delay
) {
912 /* remove any submenu open requests */
913 ob_main_loop_timeout_remove(ob_main_loop
,
914 menu_entry_frame_submenu_timeout
);
916 if ((it
= g_list_last(menu_frame_visible
)))
917 menu_frame_hide(it
->data
);
920 void menu_frame_hide_all_client(ObClient
*client
)
922 GList
*it
= g_list_last(menu_frame_visible
);
924 ObMenuFrame
*f
= it
->data
;
925 if (f
->client
== client
)
931 ObMenuFrame
* menu_frame_under(gint x
, gint y
)
933 ObMenuFrame
*ret
= NULL
;
936 for (it
= menu_frame_visible
; it
; it
= g_list_next(it
)) {
937 ObMenuFrame
*f
= it
->data
;
939 if (RECT_CONTAINS(f
->area
, x
, y
)) {
947 ObMenuEntryFrame
* menu_entry_frame_under(gint x
, gint y
)
950 ObMenuEntryFrame
*ret
= NULL
;
953 if ((frame
= menu_frame_under(x
, y
))) {
954 x
-= ob_rr_theme
->mbwidth
+ frame
->area
.x
;
955 y
-= ob_rr_theme
->mbwidth
+ frame
->area
.y
;
957 for (it
= frame
->entries
; it
; it
= g_list_next(it
)) {
958 ObMenuEntryFrame
*e
= it
->data
;
960 if (RECT_CONTAINS(e
->area
, x
, y
)) {
969 static gboolean
menu_entry_frame_submenu_timeout(gpointer data
)
971 menu_entry_frame_show_submenu((ObMenuEntryFrame
*)data
);
975 void menu_frame_select(ObMenuFrame
*self
, ObMenuEntryFrame
*entry
,
978 ObMenuEntryFrame
*old
= self
->selected
;
979 ObMenuFrame
*oldchild
= self
->child
;
981 if (entry
&& entry
->entry
->type
== OB_MENU_ENTRY_TYPE_SEPARATOR
)
984 if (old
== entry
) return;
986 if (config_submenu_show_delay
) {
987 /* remove any submenu open requests */
988 ob_main_loop_timeout_remove(ob_main_loop
,
989 menu_entry_frame_submenu_timeout
);
992 self
->selected
= entry
;
995 menu_entry_frame_render(old
);
997 menu_frame_hide(oldchild
);
999 if (self
->selected
) {
1000 menu_entry_frame_render(self
->selected
);
1002 if (self
->selected
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
) {
1003 if (config_submenu_show_delay
&& !immediate
) {
1004 /* initiate a new submenu open request */
1005 ob_main_loop_timeout_add(ob_main_loop
,
1006 config_submenu_show_delay
* 1000,
1007 menu_entry_frame_submenu_timeout
,
1008 self
->selected
, g_direct_equal
,
1011 menu_entry_frame_show_submenu(self
->selected
);
1017 void menu_entry_frame_show_submenu(ObMenuEntryFrame
*self
)
1021 if (!self
->entry
->data
.submenu
.submenu
) return;
1023 f
= menu_frame_new(self
->entry
->data
.submenu
.submenu
,
1024 self
->frame
->client
);
1025 /* pass our direction on to our child */
1026 f
->direction_right
= self
->frame
->direction_right
;
1028 menu_frame_show_submenu(f
, self
->frame
, self
);
1031 void menu_entry_frame_execute(ObMenuEntryFrame
*self
, guint state
, Time time
)
1033 if (self
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
&&
1034 self
->entry
->data
.normal
.enabled
)
1036 /* grab all this shizzle, cuz when the menu gets hidden, 'self'
1038 ObMenuEntry
*entry
= self
->entry
;
1039 ObMenuExecuteFunc func
= self
->frame
->menu
->execute_func
;
1040 gpointer data
= self
->frame
->menu
->data
;
1041 GSList
*acts
= self
->entry
->data
.normal
.actions
;
1042 ObClient
*client
= self
->frame
->client
;
1044 /* release grabs before executing the shit */
1045 if (!(state
& ControlMask
))
1046 menu_frame_hide_all();
1049 func(entry
, state
, data
, time
);
1051 action_run(acts
, client
, state
, time
);
1055 void menu_frame_select_previous(ObMenuFrame
*self
)
1057 GList
*it
= NULL
, *start
;
1059 if (self
->entries
) {
1060 start
= it
= g_list_find(self
->entries
, self
->selected
);
1062 ObMenuEntryFrame
*e
;
1064 it
= it
? g_list_previous(it
) : g_list_last(self
->entries
);
1070 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1072 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1077 menu_frame_select(self
, it
? it
->data
: NULL
, TRUE
);
1080 void menu_frame_select_next(ObMenuFrame
*self
)
1082 GList
*it
= NULL
, *start
;
1084 if (self
->entries
) {
1085 start
= it
= g_list_find(self
->entries
, self
->selected
);
1087 ObMenuEntryFrame
*e
;
1089 it
= it
? g_list_next(it
) : self
->entries
;
1095 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_SUBMENU
)
1097 if (e
->entry
->type
== OB_MENU_ENTRY_TYPE_NORMAL
)
1102 menu_frame_select(self
, it
? it
->data
: NULL
, TRUE
);