]> Dogcows Code - chaz/openbox/blob - openbox/menuframe.c
improve submenu hide delay
[chaz/openbox] / openbox / menuframe.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 menuframe.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "menuframe.h"
21 #include "client.h"
22 #include "menu.h"
23 #include "screen.h"
24 #include "prop.h"
25 #include "actions.h"
26 #include "event.h"
27 #include "grab.h"
28 #include "openbox.h"
29 #include "mainloop.h"
30 #include "config.h"
31 #include "render/theme.h"
32
33 #define PADDING 2
34 #define MAX_MENU_WIDTH 400
35
36 #define ITEM_HEIGHT (ob_rr_theme->menu_font_height + 2*PADDING)
37
38 #define FRAME_EVENTMASK (ButtonPressMask |ButtonMotionMask | EnterWindowMask |\
39 LeaveWindowMask)
40 #define ENTRY_EVENTMASK (EnterWindowMask | LeaveWindowMask | \
41 ButtonPressMask | ButtonReleaseMask)
42
43 GList *menu_frame_visible;
44 GHashTable *menu_frame_map;
45
46 static RrAppearance *a_sep;
47
48 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
49 ObMenuFrame *frame);
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);
54
55 static gboolean menu_entry_frame_submenu_hide_timeout(gpointer data);
56
57 static Window createWindow(Window parent, gulong mask,
58 XSetWindowAttributes *attrib)
59 {
60 return XCreateWindow(ob_display, parent, 0, 0, 1, 1, 0,
61 RrDepth(ob_rr_inst), InputOutput,
62 RrVisual(ob_rr_inst), mask, attrib);
63 }
64
65 void menu_frame_startup(gboolean reconfig)
66 {
67 gint i;
68
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;
75 }
76
77 if (reconfig) return;
78
79 menu_frame_map = g_hash_table_new(g_int_hash, g_int_equal);
80 }
81
82 void menu_frame_shutdown(gboolean reconfig)
83 {
84 RrAppearanceFree(a_sep);
85
86 if (reconfig) return;
87
88 g_hash_table_destroy(menu_frame_map);
89 }
90
91 ObMenuFrame* menu_frame_new(ObMenu *menu, guint show_from, ObClient *client)
92 {
93 ObMenuFrame *self;
94 XSetWindowAttributes attr;
95
96 self = g_new0(ObMenuFrame, 1);
97 self->type = Window_Menu;
98 self->menu = menu;
99 self->selected = NULL;
100 self->client = client;
101 self->direction_right = TRUE;
102 self->show_from = show_from;
103
104 attr.event_mask = FRAME_EVENTMASK;
105 self->window = createWindow(RootWindow(ob_display, ob_screen),
106 CWEventMask, &attr);
107
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);
111
112 XSetWindowBorderWidth(ob_display, self->window, ob_rr_theme->mbwidth);
113 XSetWindowBorder(ob_display, self->window,
114 RrColorPixel(ob_rr_theme->menu_border_color));
115
116 self->a_items = RrAppearanceCopy(ob_rr_theme->a_menu);
117
118 stacking_add(MENU_AS_WINDOW(self));
119
120 return self;
121 }
122
123 void menu_frame_free(ObMenuFrame *self)
124 {
125 if (self) {
126 while (self->entries) {
127 menu_entry_frame_free(self->entries->data);
128 self->entries = g_list_delete_link(self->entries, self->entries);
129 }
130
131 stacking_remove(MENU_AS_WINDOW(self));
132
133 RrAppearanceFree(self->a_items);
134
135 XDestroyWindow(ob_display, self->window);
136
137 g_free(self);
138 }
139 }
140
141 static ObMenuEntryFrame* menu_entry_frame_new(ObMenuEntry *entry,
142 ObMenuFrame *frame)
143 {
144 ObMenuEntryFrame *self;
145 XSetWindowAttributes attr;
146
147 self = g_new0(ObMenuEntryFrame, 1);
148 self->entry = entry;
149 self->frame = frame;
150
151 menu_entry_ref(entry);
152
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);
161 }
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);
165 }
166
167 XMapWindow(ob_display, self->window);
168 XMapWindow(ob_display, self->text);
169
170 return self;
171 }
172
173 static void menu_entry_frame_free(ObMenuEntryFrame *self)
174 {
175 if (self) {
176 menu_entry_unref(self->entry);
177
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);
185 }
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);
189 }
190
191 g_free(self);
192 }
193 }
194
195 void menu_frame_move(ObMenuFrame *self, gint x, gint y)
196 {
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);
200 }
201
202 static void menu_frame_place_topmenu(ObMenuFrame *self, gint *x, gint *y)
203 {
204 gint dx, dy;
205
206 if (config_menu_middle) {
207 gint myx;
208
209 myx = *x;
210 *y -= self->area.height / 2;
211
212 /* try to the right of the cursor */
213 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
214 self->direction_right = TRUE;
215 if (dx != 0) {
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;
220 }
221 if (dx != 0) {
222 /* if didnt fit on either side so just use what it says */
223 myx = *x;
224 menu_frame_move_on_screen(self, myx, *y, &dx, &dy);
225 self->direction_right = TRUE;
226 }
227 *x = myx + dx;
228 *y += dy;
229 } else {
230 gint myx, myy;
231
232 myx = *x;
233 myy = *y;
234
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;
241 myy = *y;
242 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
243 self->direction_right = FALSE;
244 }
245 if (dx != 0 || dy != 0) {
246 /* try to the top right of the cursor */
247 myx = *x;
248 myy = *y - self->area.height;
249 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
250 self->direction_right = TRUE;
251 }
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;
258 }
259 if (dx != 0 || dy != 0) {
260 /* if didnt fit on either side so just use what it says */
261 myx = *x;
262 myy = *y;
263 menu_frame_move_on_screen(self, myx, myy, &dx, &dy);
264 self->direction_right = TRUE;
265 }
266 *x = myx + dx;
267 *y = myy + dy;
268 }
269 }
270
271 static void menu_frame_place_submenu(ObMenuFrame *self, gint *x, gint *y)
272 {
273 gint overlapx, overlapy;
274 gint bwidth;
275
276 overlapx = ob_rr_theme->menu_overlap_x;
277 overlapy = ob_rr_theme->menu_overlap_y;
278 bwidth = ob_rr_theme->mbwidth;
279
280 if (self->direction_right)
281 *x = self->parent->area.x + self->parent->area.width -
282 overlapx - bwidth;
283 else
284 *x = self->parent->area.x - self->area.width + overlapx + bwidth;
285
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;
289 else
290 *y += overlapy;
291 }
292
293 void menu_frame_move_on_screen(ObMenuFrame *self, gint x, gint y,
294 gint *dx, gint *dy)
295 {
296 Rect *a = NULL;
297 gint pos, half;
298
299 *dx = *dy = 0;
300
301 a = screen_physical_area_monitor(screen_find_monitor_point(x, y));
302
303 half = g_list_length(self->entries) / 2;
304 pos = g_list_index(self->entries, self->selected);
305
306 /* if in the bottom half then check this stuff first, will keep the bottom
307 edge of the menu visible */
308 if (pos > half) {
309 *dx = MAX(*dx, a->x - x);
310 *dy = MAX(*dy, a->y - y);
311 }
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 */
316 if (pos <= half) {
317 *dx = MAX(*dx, a->x - x);
318 *dy = MAX(*dy, a->y - y);
319 }
320
321 g_free(a);
322 }
323
324 static void menu_entry_frame_render(ObMenuEntryFrame *self)
325 {
326 RrAppearance *item_a, *text_a;
327 gint th; /* temp */
328 ObMenu *sub;
329 ObMenuFrame *frame = self->frame;
330
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 ?
336 /* disabled */
337 (self == self->frame->selected ?
338 ob_rr_theme->a_menu_disabled_selected :
339 ob_rr_theme->a_menu_disabled) :
340 /* enabled */
341 (self == self->frame->selected ?
342 ob_rr_theme->a_menu_selected :
343 ob_rr_theme->a_menu_normal));
344 th = ITEM_HEIGHT;
345 break;
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;
350 } else {
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;
354 }
355 break;
356 default:
357 g_assert_not_reached();
358 }
359
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);
367
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 ?
372 /* disabled */
373 (self == self->frame->selected ?
374 ob_rr_theme->a_menu_text_disabled_selected :
375 ob_rr_theme->a_menu_text_disabled) :
376 /* enabled */
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))
385 {
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;
389 } else
390 text_a->texture[0].data.text.shortcut = FALSE;
391 break;
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))
401 {
402 text_a->texture[0].data.text.shortcut = TRUE;
403 text_a->texture[0].data.text.shortcut_pos = sub->shortcut_position;
404 } else
405 text_a->texture[0].data.text.shortcut = FALSE;
406 break;
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;
412 }
413 else
414 text_a = ob_rr_theme->a_menu_text_normal;
415 break;
416 }
417
418 switch (self->entry->type) {
419 case OB_MENU_ENTRY_TYPE_NORMAL:
420 XMoveResizeWindow(ob_display, self->text,
421 self->frame->text_x, PADDING,
422 self->frame->text_w,
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);
429 break;
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);
440 break;
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);
456 } else {
457 gint i;
458
459 /* unlabeled separator */
460 XMoveResizeWindow(ob_display, self->text, 0, 0,
461 self->area.width,
462 ob_rr_theme->menu_sep_width +
463 2*ob_rr_theme->menu_sep_paddingy);
464
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;
477 }
478
479 RrPaint(a_sep, self->text, self->area.width,
480 ob_rr_theme->menu_sep_width +
481 2*ob_rr_theme->menu_sep_paddingy);
482 }
483 break;
484 }
485
486 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
487 self->entry->data.normal.icon)
488 {
489 RrAppearance *clear;
490
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);
497
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)
516 {
517 RrColor *c;
518 RrAppearance *clear;
519
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);
526
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;
532
533 c = (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
534 !self->entry->data.normal.enabled ?
535 /* disabled */
536 (self == self->frame->selected ?
537 self->entry->data.normal.mask_disabled_selected_color :
538 self->entry->data.normal.mask_disabled_color) :
539 /* enabled */
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;
544
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);
554 } else
555 XUnmapWindow(ob_display, self->icon);
556
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);
575 } else
576 XUnmapWindow(ob_display, self->bullet);
577
578 XFlush(ob_display);
579 }
580
581 /*! this code is taken from the menu_frame_render. if that changes, this won't
582 work.. */
583 static gint menu_entry_frame_get_height(ObMenuEntryFrame *self,
584 gboolean first_entry,
585 gboolean last_entry)
586 {
587 ObMenuEntryType t;
588 gint h = 0;
589
590 h += 2*PADDING;
591
592 if (self)
593 t = self->entry->type;
594 else
595 /* this is the More... entry, it's NORMAL type */
596 t = OB_MENU_ENTRY_TYPE_NORMAL;
597
598 switch (t) {
599 case OB_MENU_ENTRY_TYPE_NORMAL:
600 case OB_MENU_ENTRY_TYPE_SUBMENU:
601 h += ob_rr_theme->menu_font_height;
602 break;
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;
607
608 /* if the first entry is a labeled separator, then make its border
609 overlap with the menu's outside border */
610 if (first_entry)
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 */
614 if (last_entry)
615 h -= ob_rr_theme->mbwidth;
616 } else {
617 h += ob_rr_theme->menu_sep_width +
618 2*ob_rr_theme->menu_sep_paddingy - PADDING * 2;
619 }
620 break;
621 }
622
623 return h;
624 }
625
626 void menu_frame_render(ObMenuFrame *self)
627 {
628 gint w = 0, h = 0;
629 gint tw, th; /* temps */
630 GList *it;
631 gboolean has_icon = FALSE;
632 ObMenu *sub;
633 ObMenuEntryFrame *e;
634
635 /* find text dimensions */
636
637 STRUT_SET(self->item_margin, 0, 0, 0, 0);
638
639 if (self->entries) {
640 gint l, t, r, b;
641
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);
645 tw += 2*PADDING;
646
647 th = ITEM_HEIGHT;
648
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));
673 }
674
675 /* render the entries */
676
677 for (it = self->entries; it; it = g_list_next(it)) {
678 RrAppearance *text_a;
679 e = it->data;
680
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)
686 {
687 h -= ob_rr_theme->mbwidth;
688 }
689
690 if (e->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR &&
691 e->entry->data.separator.label)
692 {
693 e->border = ob_rr_theme->mbwidth;
694 }
695
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));
702
703 text_a = (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
704 !e->entry->data.normal.enabled ?
705 /* disabled */
706 (e == self->selected ?
707 ob_rr_theme->a_menu_text_disabled_selected :
708 ob_rr_theme->a_menu_text_disabled) :
709 /* enabled */
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;
719
720 if (e->entry->data.normal.icon ||
721 e->entry->data.normal.mask)
722 has_icon = TRUE;
723 break;
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;
730
731 if (e->entry->data.normal.icon ||
732 e->entry->data.normal.mask)
733 has_icon = TRUE;
734
735 tw += ITEM_HEIGHT - PADDING;
736 break;
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;
746 } else {
747 tw = 0;
748 th = ob_rr_theme->menu_sep_width +
749 2*ob_rr_theme->menu_sep_paddingy - 2*PADDING;
750 }
751 break;
752 }
753 tw += 2*PADDING;
754 th += 2*PADDING;
755 w = MAX(w, tw);
756 h += th;
757 }
758
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)
765 {
766 h -= ob_rr_theme->mbwidth;
767 }
768
769 self->text_x = PADDING;
770 self->text_w = w;
771
772 if (self->entries) {
773 if (has_icon) {
774 w += ITEM_HEIGHT + PADDING;
775 self->text_x += ITEM_HEIGHT + PADDING;
776 }
777 }
778
779 if (!w) w = 10;
780 if (!h) h = 3;
781
782 XResizeWindow(ob_display, self->window, w, h);
783
784 self->inner_w = w;
785
786 RrPaint(self->a_items, self->window, w, h);
787
788 for (it = self->entries; it; it = g_list_next(it))
789 menu_entry_frame_render(it->data);
790
791 w += ob_rr_theme->mbwidth * 2;
792 h += ob_rr_theme->mbwidth * 2;
793
794 RECT_SET_SIZE(self->area, w, h);
795
796 XFlush(ob_display);
797 }
798
799 static void menu_frame_update(ObMenuFrame *self)
800 {
801 GList *mit, *fit;
802 Rect *a;
803 gint h;
804
805 menu_pipe_execute(self->menu);
806 menu_find_submenus(self->menu);
807
808 self->selected = NULL;
809
810 /* start at show_from */
811 mit = g_list_nth(self->menu->entries, self->show_from);
812
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))
817 {
818 ObMenuEntryFrame *f = fit->data;
819 f->entry = mit->data;
820 }
821
822 /* if there are more menu entries than in the frame, add them */
823 while (mit) {
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);
827 }
828
829 /* if there are more frame entries than menu entries then get rid of
830 them */
831 while (fit) {
832 GList *n = g_list_next(fit);
833 menu_entry_frame_free(fit->data);
834 self->entries = g_list_delete_link(self->entries, fit);
835 fit = n;
836 }
837
838 /* * make the menu fit on the screen */
839
840 /* calculate the height of the menu */
841 h = 0;
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;
848
849 a = screen_physical_area_monitor(self->monitor);
850
851 if (h > a->height) {
852 GList *flast, *tmp;
853 gboolean last_entry = TRUE;
854
855 /* take the height of our More... entry into account */
856 h += menu_entry_frame_get_height(NULL, FALSE, TRUE);
857
858 /* start at the end of the entries */
859 flast = g_list_last(self->entries);
860
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);
866
867 /* destroy the entry we're not displaying */
868 tmp = flast;
869 flast = g_list_previous(flast);
870 menu_entry_frame_free(tmp->data);
871 self->entries = g_list_delete_link(self->entries, tmp);
872
873 /* only the first one that we see is the last entry in the menu */
874 last_entry = FALSE;
875 };
876
877 {
878 ObMenuEntry *more_entry;
879 ObMenuEntryFrame *more_frame;
880 /* make the More... menu entry frame which will display in this
881 frame.
882 if self->menu->more_menu is NULL that means that this is already
883 More... menu, so just use ourself.
884 */
885 more_entry = menu_get_more((self->menu->more_menu ?
886 self->menu->more_menu :
887 self->menu),
888 /* continue where we left off */
889 self->show_from +
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);
894
895 /* add our More... entry to the frame */
896 self->entries = g_list_append(self->entries, more_frame);
897 }
898 }
899
900 g_free(a);
901
902 menu_frame_render(self);
903 }
904
905 static gboolean menu_frame_is_visible(ObMenuFrame *self)
906 {
907 return !!(g_list_find(menu_frame_visible, self));
908 }
909
910 static gboolean menu_frame_show(ObMenuFrame *self)
911 {
912 GList *it;
913
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)
918 break;
919 }
920 if (!it) {
921 if (self->menu->update_func)
922 if (!self->menu->update_func(self, self->menu->data))
923 return FALSE;
924 }
925
926 if (menu_frame_visible == NULL) {
927 /* no menus shown yet */
928
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))
932 return FALSE;
933 if (!grab_keyboard()) {
934 ungrab_pointer();
935 return FALSE;
936 }
937 }
938
939 menu_frame_update(self);
940
941 menu_frame_visible = g_list_prepend(menu_frame_visible, self);
942
943 if (self->menu->show_func)
944 self->menu->show_func(self, self->menu->data);
945
946 return TRUE;
947 }
948
949 gboolean menu_frame_show_topmenu(ObMenuFrame *self, gint x, gint y,
950 gboolean mouse)
951 {
952 gint px, py;
953
954 if (menu_frame_is_visible(self))
955 return TRUE;
956 if (!menu_frame_show(self))
957 return FALSE;
958
959 if (self->menu->place_func)
960 self->menu->place_func(self, &x, &y, mouse, self->menu->data);
961 else
962 menu_frame_place_topmenu(self, &x, &y);
963
964 menu_frame_move(self, x, y);
965
966 XMapWindow(ob_display, self->window);
967
968 if (screen_pointer_pos(&px, &py)) {
969 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
970 if (e && e->frame == self)
971 e->ignore_enters++;
972 }
973
974 return TRUE;
975 }
976
977 static void remove_submenu_hide_timeout(ObMenuFrame *self /* parent of submenu to hide */)
978 {
979 ob_main_loop_timeout_remove(ob_main_loop,
980 menu_entry_frame_submenu_hide_timeout);
981 if (self)
982 self->submenu_to_hide = NULL;
983 }
984
985 gboolean menu_frame_show_submenu(ObMenuFrame *self, ObMenuFrame *parent,
986 ObMenuEntryFrame *parent_entry)
987 {
988 gint x, y, dx, dy;
989 gint px, py;
990
991 if (menu_frame_is_visible(self))
992 return TRUE;
993
994 self->monitor = parent->monitor;
995 self->parent = parent;
996 self->parent_entry = parent_entry;
997
998 remove_submenu_hide_timeout(parent);
999
1000 /* set up parent's child to be us */
1001 if ((parent->child) != self) {
1002 if (parent->child)
1003 menu_frame_hide(parent->child);
1004 parent->child = self;
1005 }
1006
1007 if (!menu_frame_show(self))
1008 return FALSE;
1009
1010 menu_frame_place_submenu(self, &x, &y);
1011 menu_frame_move_on_screen(self, x, y, &dx, &dy);
1012
1013 if (dx != 0) {
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);
1018 }
1019 menu_frame_move(self, x + dx, y + dy);
1020
1021 XMapWindow(ob_display, self->window);
1022
1023 if (screen_pointer_pos(&px, &py)) {
1024 ObMenuEntryFrame *e = menu_entry_frame_under(px, py);
1025 if (e && e->frame == self)
1026 e->ignore_enters++;
1027 }
1028
1029 return TRUE;
1030 }
1031
1032 static void menu_frame_hide(ObMenuFrame *self)
1033 {
1034 GList *it = g_list_find(menu_frame_visible, self);
1035 gulong ignore_start;
1036
1037 remove_submenu_hide_timeout(self->parent);
1038
1039 if (!it)
1040 return;
1041
1042 if (self->menu->hide_func)
1043 self->menu->hide_func(self, self->menu->data);
1044
1045 if (self->child)
1046 menu_frame_hide(self->child);
1047
1048 if (self->parent)
1049 self->parent->child = NULL;
1050 self->parent = NULL;
1051 self->parent_entry = NULL;
1052
1053 menu_frame_visible = g_list_delete_link(menu_frame_visible, it);
1054
1055 if (menu_frame_visible == NULL) {
1056 /* last menu shown */
1057 ungrab_pointer();
1058 ungrab_keyboard();
1059 }
1060
1061 ignore_start = event_start_ignore_all_enters();
1062 XUnmapWindow(ob_display, self->window);
1063 event_end_ignore_all_enters(ignore_start);
1064
1065 menu_frame_free(self);
1066 }
1067
1068 void menu_frame_hide_all(void)
1069 {
1070 GList *it;
1071
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);
1076 }
1077 if ((it = g_list_last(menu_frame_visible)))
1078 menu_frame_hide(it->data);
1079 }
1080
1081 void menu_frame_hide_all_client(ObClient *client)
1082 {
1083 GList *it = g_list_last(menu_frame_visible);
1084 if (it) {
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);
1091 }
1092 menu_frame_hide(f);
1093 }
1094 }
1095 }
1096
1097 ObMenuFrame* menu_frame_under(gint x, gint y)
1098 {
1099 ObMenuFrame *ret = NULL;
1100 GList *it;
1101
1102 for (it = menu_frame_visible; it; it = g_list_next(it)) {
1103 ObMenuFrame *f = it->data;
1104
1105 if (RECT_CONTAINS(f->area, x, y)) {
1106 ret = f;
1107 break;
1108 }
1109 }
1110 return ret;
1111 }
1112
1113 ObMenuEntryFrame* menu_entry_frame_under(gint x, gint y)
1114 {
1115 ObMenuFrame *frame;
1116 ObMenuEntryFrame *ret = NULL;
1117 GList *it;
1118
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;
1122
1123 for (it = frame->entries; it; it = g_list_next(it)) {
1124 ObMenuEntryFrame *e = it->data;
1125
1126 if (RECT_CONTAINS(e->area, x, y)) {
1127 ret = e;
1128 break;
1129 }
1130 }
1131 }
1132 return ret;
1133 }
1134
1135 static gboolean menu_entry_frame_submenu_timeout(gpointer data)
1136 {
1137 g_assert(menu_frame_visible);
1138 menu_entry_frame_show_submenu((ObMenuEntryFrame*)data);
1139 return FALSE;
1140 }
1141
1142 static gboolean menu_entry_frame_submenu_hide_timeout(gpointer data)
1143 {
1144 menu_frame_hide((ObMenuFrame*)data);
1145 return FALSE;
1146 }
1147
1148 void menu_frame_select(ObMenuFrame *self, ObMenuEntryFrame *entry,
1149 gboolean immediate)
1150 {
1151 ObMenuEntryFrame *old = self->selected;
1152 ObMenuFrame *oldchild = self->child;
1153 ObMenuEntryFrame *temp;
1154 gboolean reselection;
1155
1156
1157 if (!oldchild) {
1158 /* self is the last visible (sub)menu */
1159 if (self->parent && self->parent_entry != self->parent->selected) {
1160 /* Legend:
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.
1166 Results:
1167 parent menu selection is "B" instead of "A",
1168 */
1169 temp = self->parent->selected;
1170 self->parent->selected = self->parent_entry;
1171 if (temp)
1172 menu_entry_frame_render(temp);
1173 menu_entry_frame_render(self->parent_entry);
1174 }
1175 remove_submenu_hide_timeout(self->parent);
1176 }
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);
1183 }
1184 }
1185
1186
1187 if (entry && entry->entry->type == OB_MENU_ENTRY_TYPE_SEPARATOR)
1188 entry = old;
1189
1190 if (old == entry) return;
1191
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);
1196 }
1197
1198 self->selected = entry;
1199
1200 if (old)
1201 menu_entry_frame_render(old);
1202
1203 reselection = FALSE;
1204 if (oldchild) {
1205 if (self->submenu_to_hide == entry) {
1206 /* Legend:
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.
1212 */
1213 reselection = TRUE;
1214 remove_submenu_hide_timeout(self);
1215 }
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,
1222 NULL);
1223 self->submenu_to_hide = old;
1224 }
1225 }
1226 else
1227 menu_frame_hide(oldchild);
1228 }
1229
1230 if (self->selected) {
1231 menu_entry_frame_render(self->selected);
1232
1233 if (!reselection &&
1234 (self->selected->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU))
1235 {
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,
1242 NULL);
1243 } else {
1244 menu_entry_frame_show_submenu(self->selected);
1245 }
1246 }
1247 }
1248 }
1249
1250 void menu_entry_frame_show_submenu(ObMenuEntryFrame *self)
1251 {
1252 ObMenuFrame *f;
1253
1254 if (!self->entry->data.submenu.submenu) return;
1255
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;
1261
1262 menu_frame_show_submenu(f, self->frame, self);
1263 }
1264
1265 void menu_entry_frame_execute(ObMenuEntryFrame *self, guint state)
1266 {
1267 if (self->entry->type == OB_MENU_ENTRY_TYPE_NORMAL &&
1268 self->entry->data.normal.enabled)
1269 {
1270 /* grab all this shizzle, cuz when the menu gets hidden, 'self'
1271 gets freed */
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;
1278
1279 /* release grabs before executing the shit */
1280 if (!(state & ControlMask)) {
1281 menu_frame_hide_all();
1282 frame = NULL;
1283 }
1284
1285 if (func)
1286 func(entry, frame, client, state, data);
1287 else
1288 actions_run_acts(acts, OB_USER_ACTION_MENU_SELECTION,
1289 state, -1, -1, 0, OB_FRAME_CONTEXT_NONE, client);
1290 }
1291 }
1292
1293 void menu_frame_select_previous(ObMenuFrame *self)
1294 {
1295 GList *it = NULL, *start;
1296
1297 if (self->entries) {
1298 start = it = g_list_find(self->entries, self->selected);
1299 while (TRUE) {
1300 ObMenuEntryFrame *e;
1301
1302 it = it ? g_list_previous(it) : g_list_last(self->entries);
1303 if (it == start)
1304 break;
1305
1306 if (it) {
1307 e = it->data;
1308 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1309 break;
1310 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1311 break;
1312 }
1313 }
1314 }
1315 menu_frame_select(self, it ? it->data : NULL, TRUE);
1316 }
1317
1318 void menu_frame_select_next(ObMenuFrame *self)
1319 {
1320 GList *it = NULL, *start;
1321
1322 if (self->entries) {
1323 start = it = g_list_find(self->entries, self->selected);
1324 while (TRUE) {
1325 ObMenuEntryFrame *e;
1326
1327 it = it ? g_list_next(it) : self->entries;
1328 if (it == start)
1329 break;
1330
1331 if (it) {
1332 e = it->data;
1333 if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
1334 break;
1335 if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
1336 break;
1337 }
1338 }
1339 }
1340 menu_frame_select(self, it ? it->data : NULL, TRUE);
1341 }
This page took 0.095658 seconds and 5 git commands to generate.