]> icculus.org git repositories - dana/openbox.git/blob - openbox/menuframe.c
improve submenu hide delay
[dana/openbox.git] / 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 }