Fix some warnings
[wmaker-crm.git] / src / menu.c
blobdafdbdd82074a1b67bf3cfb4aedb5c7e9f246391
1 /* menu.c- generic menu, used for root menu, application menus etc.
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 * Copyright (c) 1998-2003 Dan Pascu
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "wconfig.h"
25 #include <X11/Xlib.h>
26 #include <X11/Xutil.h>
27 #include <X11/keysym.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stdio.h>
31 #include <stdint.h>
32 #include <unistd.h>
33 #include <ctype.h>
35 #include "WindowMaker.h"
36 #include "wcore.h"
37 #include "framewin.h"
38 #include "menu.h"
39 #include "actions.h"
40 #include "funcs.h"
41 #include "stacking.h"
42 #include "text.h"
43 #include "xinerama.h"
44 #include "workspace.h"
46 /****** Global Variables ******/
48 extern Cursor wCursor[WCUR_LAST];
50 extern XContext wWinContext;
52 extern WPreferences wPreferences;
54 #define MOD_MASK wPreferences.modifier_mask
56 #define MENU_SCROLL_STEP menuScrollParameters[(int)wPreferences.menu_scroll_speed].steps
57 #define MENU_SCROLL_DELAY menuScrollParameters[(int)wPreferences.menu_scroll_speed].delay
59 #define MENUW(m) ((m)->frame->core->width+2*FRAME_BORDER_WIDTH)
60 #define MENUH(m) ((m)->frame->core->height+2*FRAME_BORDER_WIDTH)
62 /***** Local Stuff ******/
64 #define WSS_ROOTMENU (1<<0)
65 #define WSS_SWITCHMENU (1<<1)
66 #define WSS_WSMENU (1<<2)
68 static struct {
69 int steps;
70 int delay;
71 } menuScrollParameters[5] = {
73 MENU_SCROLL_STEPS_UF, MENU_SCROLL_DELAY_UF}, {
74 MENU_SCROLL_STEPS_F, MENU_SCROLL_DELAY_F}, {
75 MENU_SCROLL_STEPS_M, MENU_SCROLL_DELAY_M}, {
76 MENU_SCROLL_STEPS_S, MENU_SCROLL_DELAY_S}, {
77 MENU_SCROLL_STEPS_US, MENU_SCROLL_DELAY_US}};
79 static void menuMouseDown(WObjDescriptor * desc, XEvent * event);
80 static void menuExpose(WObjDescriptor * desc, XEvent * event);
81 static void menuTitleDoubleClick(WCoreWindow * sender, void *data, XEvent * event);
82 static void menuTitleMouseDown(WCoreWindow * sender, void *data, XEvent * event);
83 static void menuCloseClick(WCoreWindow * sender, void *data, XEvent * event);
84 static void updateTexture(WMenu * menu);
85 static int saveMenuRecurs(WMPropList * menus, WScreen * scr, WMenu * menu);
86 static int restoreMenuRecurs(WScreen * scr, WMPropList * menus, WMenu * menu, char *path);
87 static void selectEntry(WMenu * menu, int entry_no);
88 static void closeCascade(WMenu * menu);
90 /****** Notification Observers ******/
92 static void appearanceObserver(void *self, WMNotification * notif)
94 WMenu *menu = (WMenu *) self;
95 uintptr_t flags = (uintptr_t)WMGetNotificationClientData(notif);
97 if (!menu->flags.realized)
98 return;
100 if (WMGetNotificationName(notif) == WNMenuAppearanceSettingsChanged) {
101 if (flags & WFontSettings) {
102 menu->flags.realized = 0;
103 wMenuRealize(menu);
105 if (flags & WTextureSettings) {
106 if (!menu->flags.brother)
107 updateTexture(menu);
109 if (flags & (WTextureSettings | WColorSettings)) {
110 wMenuPaint(menu);
112 } else if (menu->flags.titled) {
114 if (flags & WFontSettings) {
115 menu->flags.realized = 0;
116 wMenuRealize(menu);
118 if (flags & WTextureSettings) {
119 menu->frame->flags.need_texture_remake = 1;
121 if (flags & (WColorSettings | WTextureSettings)) {
122 wFrameWindowPaint(menu->frame);
127 /************************************/
130 *----------------------------------------------------------------------
131 * wMenuCreate--
132 * Creates a new empty menu with the specified title. If main_menu
133 * is True, the created menu will be a main menu, which has some special
134 * properties such as being placed over other normal menus.
135 * If title is NULL, the menu will have no titlebar.
137 * Returns:
138 * The created menu.
139 *----------------------------------------------------------------------
141 WMenu *wMenuCreate(WScreen * screen, char *title, int main_menu)
143 WMenu *menu;
144 static int brother = 0;
145 int tmp, flags;
147 menu = wmalloc(sizeof(WMenu));
149 memset(menu, 0, sizeof(WMenu));
151 #ifdef SINGLE_MENULEVEL
152 tmp = WMSubmenuLevel;
153 #else
154 tmp = (main_menu ? WMMainMenuLevel : WMSubmenuLevel);
155 #endif
157 flags = WFF_SINGLE_STATE | WFF_BORDER;
158 if (title) {
159 flags |= WFF_TITLEBAR | WFF_RIGHT_BUTTON;
160 menu->flags.titled = 1;
162 menu->frame =
163 wFrameWindowCreate(screen, tmp, 8, 2, 1, 1, &wPreferences.menu_title_clearance, flags,
164 screen->menu_title_texture, NULL,
165 screen->menu_title_color, &screen->menu_title_font);
167 menu->frame->core->descriptor.parent = menu;
168 menu->frame->core->descriptor.parent_type = WCLASS_MENU;
169 menu->frame->core->descriptor.handle_mousedown = menuMouseDown;
171 wFrameWindowHideButton(menu->frame, WFF_RIGHT_BUTTON);
173 if (title) {
174 menu->frame->title = wstrdup(title);
177 menu->frame->flags.justification = WTJ_LEFT;
179 menu->frame->rbutton_image = screen->b_pixmaps[WBUT_CLOSE];
181 menu->entry_no = 0;
182 menu->alloced_entries = 0;
183 menu->selected_entry = -1;
184 menu->entries = NULL;
186 menu->frame_x = screen->app_menu_x;
187 menu->frame_y = screen->app_menu_y;
189 menu->frame->child = menu;
191 menu->flags.lowered = 0;
193 /* create borders */
194 if (title) {
195 /* setup object descriptors */
196 menu->frame->on_mousedown_titlebar = menuTitleMouseDown;
197 menu->frame->on_dblclick_titlebar = menuTitleDoubleClick;
200 menu->frame->on_click_right = menuCloseClick;
202 menu->menu = wCoreCreate(menu->frame->core, 0, menu->frame->top_width, menu->frame->core->width, 10);
204 menu->menu->descriptor.parent = menu;
205 menu->menu->descriptor.parent_type = WCLASS_MENU;
206 menu->menu->descriptor.handle_expose = menuExpose;
207 menu->menu->descriptor.handle_mousedown = menuMouseDown;
209 menu->menu_texture_data = None;
211 XMapWindow(dpy, menu->menu->window);
213 XFlush(dpy);
215 if (!brother) {
216 brother = 1;
217 menu->brother = wMenuCreate(screen, title, main_menu);
218 brother = 0;
219 menu->brother->flags.brother = 1;
220 menu->brother->brother = menu;
222 WMAddNotificationObserver(appearanceObserver, menu, WNMenuAppearanceSettingsChanged, menu);
224 WMAddNotificationObserver(appearanceObserver, menu, WNMenuTitleAppearanceSettingsChanged, menu);
226 return menu;
229 WMenu *wMenuCreateForApp(WScreen * screen, char *title, int main_menu)
231 WMenu *menu;
233 menu = wMenuCreate(screen, title, main_menu);
234 if (!menu)
235 return NULL;
236 menu->flags.app_menu = 1;
237 menu->brother->flags.app_menu = 1;
239 return menu;
242 static void insertEntry(WMenu * menu, WMenuEntry * entry, int index)
244 int i;
246 for (i = menu->entry_no - 1; i >= index; i--) {
247 menu->entries[i]->order++;
248 menu->entries[i + 1] = menu->entries[i];
250 menu->entries[index] = entry;
253 WMenuEntry *wMenuInsertCallback(WMenu * menu, int index, char *text,
254 void (*callback) (WMenu * menu, WMenuEntry * entry), void *clientdata)
256 WMenuEntry *entry;
258 menu->flags.realized = 0;
259 menu->brother->flags.realized = 0;
261 /* reallocate array if it's too small */
262 if (menu->entry_no >= menu->alloced_entries) {
263 void *tmp;
265 tmp = wrealloc(menu->entries, sizeof(WMenuEntry) * (menu->alloced_entries + 5));
267 menu->entries = tmp;
268 menu->alloced_entries += 5;
270 menu->brother->entries = tmp;
271 menu->brother->alloced_entries = menu->alloced_entries;
273 entry = wmalloc(sizeof(WMenuEntry));
274 memset(entry, 0, sizeof(WMenuEntry));
275 entry->flags.enabled = 1;
276 entry->text = wstrdup(text);
277 entry->cascade = -1;
278 entry->clientdata = clientdata;
279 entry->callback = callback;
280 if (index < 0 || index >= menu->entry_no) {
281 entry->order = menu->entry_no;
282 menu->entries[menu->entry_no] = entry;
283 } else {
284 entry->order = index;
285 insertEntry(menu, entry, index);
288 menu->entry_no++;
289 menu->brother->entry_no = menu->entry_no;
291 return entry;
294 void wMenuEntrySetCascade(WMenu * menu, WMenuEntry * entry, WMenu * cascade)
296 WMenu *brother = menu->brother;
297 int i, done;
299 assert(menu->flags.brother == 0);
301 if (entry->cascade >= 0) {
302 menu->flags.realized = 0;
303 brother->flags.realized = 0;
306 cascade->parent = menu;
308 cascade->brother->parent = brother;
310 done = 0;
311 for (i = 0; i < menu->cascade_no; i++) {
312 if (menu->cascades[i] == NULL) {
313 menu->cascades[i] = cascade;
314 brother->cascades[i] = cascade->brother;
315 done = 1;
316 entry->cascade = i;
317 break;
320 if (!done) {
321 entry->cascade = menu->cascade_no;
323 menu->cascades = wrealloc(menu->cascades, sizeof(WMenu) * (menu->cascade_no + 1));
324 menu->cascades[menu->cascade_no++] = cascade;
326 brother->cascades = wrealloc(brother->cascades, sizeof(WMenu) * (brother->cascade_no + 1));
327 brother->cascades[brother->cascade_no++] = cascade->brother;
330 if (menu->flags.lowered) {
332 cascade->flags.lowered = 1;
333 ChangeStackingLevel(cascade->frame->core, WMNormalLevel);
335 cascade->brother->flags.lowered = 1;
336 ChangeStackingLevel(cascade->brother->frame->core, WMNormalLevel);
339 if (!menu->flags.realized)
340 wMenuRealize(menu);
343 void wMenuEntryRemoveCascade(WMenu * menu, WMenuEntry * entry)
345 assert(menu->flags.brother == 0);
347 /* destroy cascade menu */
348 if (entry->cascade >= 0 && menu->cascades && menu->cascades[entry->cascade] != NULL) {
350 wMenuDestroy(menu->cascades[entry->cascade], True);
352 menu->cascades[entry->cascade] = NULL;
353 menu->brother->cascades[entry->cascade] = NULL;
355 entry->cascade = -1;
359 void wMenuRemoveItem(WMenu * menu, int index)
361 int i;
363 if (menu->flags.brother) {
364 wMenuRemoveItem(menu->brother, index);
365 return;
368 if (index >= menu->entry_no)
369 return;
371 /* destroy cascade menu */
372 wMenuEntryRemoveCascade(menu, menu->entries[index]);
374 /* destroy unshared data */
376 if (menu->entries[index]->text)
377 wfree(menu->entries[index]->text);
379 if (menu->entries[index]->rtext)
380 wfree(menu->entries[index]->rtext);
382 if (menu->entries[index]->free_cdata && menu->entries[index]->clientdata)
383 (*menu->entries[index]->free_cdata) (menu->entries[index]->clientdata);
385 wfree(menu->entries[index]);
387 for (i = index; i < menu->entry_no - 1; i++) {
388 menu->entries[i + 1]->order--;
389 menu->entries[i] = menu->entries[i + 1];
391 menu->entry_no--;
392 menu->brother->entry_no--;
395 static Pixmap renderTexture(WMenu * menu)
397 RImage *img;
398 Pixmap pix;
399 int i;
400 RColor light;
401 RColor dark;
402 RColor mid;
403 WScreen *scr = menu->menu->screen_ptr;
404 WTexture *texture = scr->menu_item_texture;
406 if (wPreferences.menu_style == MS_NORMAL) {
407 img = wTextureRenderImage(texture, menu->menu->width, menu->entry_height, WREL_MENUENTRY);
408 } else {
409 img = wTextureRenderImage(texture, menu->menu->width, menu->menu->height + 1, WREL_MENUENTRY);
411 if (!img) {
412 wwarning(_("could not render texture: %s"), RMessageForError(RErrorCode));
414 return None;
417 if (wPreferences.menu_style == MS_SINGLE_TEXTURE) {
418 light.alpha = 0;
419 light.red = light.green = light.blue = 80;
421 dark.alpha = 255;
422 dark.red = dark.green = dark.blue = 0;
424 mid.alpha = 0;
425 mid.red = mid.green = mid.blue = 40;
427 for (i = 1; i < menu->entry_no; i++) {
428 ROperateLine(img, RSubtractOperation, 0, i * menu->entry_height - 2,
429 menu->menu->width - 1, i * menu->entry_height - 2, &mid);
431 RDrawLine(img, 0, i * menu->entry_height - 1,
432 menu->menu->width - 1, i * menu->entry_height - 1, &dark);
434 ROperateLine(img, RAddOperation, 0, i * menu->entry_height,
435 menu->menu->width - 1, i * menu->entry_height, &light);
438 if (!RConvertImage(scr->rcontext, img, &pix)) {
439 wwarning(_("error rendering image:%s"), RMessageForError(RErrorCode));
441 RReleaseImage(img);
443 return pix;
446 static void updateTexture(WMenu * menu)
448 WScreen *scr = menu->menu->screen_ptr;
450 /* setup background texture */
451 if (scr->menu_item_texture->any.type != WTEX_SOLID) {
452 if (!menu->flags.brother) {
453 FREE_PIXMAP(menu->menu_texture_data);
455 menu->menu_texture_data = renderTexture(menu);
457 XSetWindowBackgroundPixmap(dpy, menu->menu->window, menu->menu_texture_data);
458 XClearWindow(dpy, menu->menu->window);
460 XSetWindowBackgroundPixmap(dpy, menu->brother->menu->window, menu->menu_texture_data);
461 XClearWindow(dpy, menu->brother->menu->window);
463 } else {
464 XSetWindowBackground(dpy, menu->menu->window, scr->menu_item_texture->any.color.pixel);
465 XClearWindow(dpy, menu->menu->window);
469 void wMenuRealize(WMenu * menu)
471 int i;
472 int width, rwidth, mrwidth, mwidth;
473 int theight, twidth, eheight;
474 WScreen *scr = menu->frame->screen_ptr;
475 static int brother_done = 0;
476 int flags;
478 if (!brother_done) {
479 brother_done = 1;
480 wMenuRealize(menu->brother);
481 brother_done = 0;
484 flags = WFF_SINGLE_STATE | WFF_BORDER;
485 if (menu->flags.titled)
486 flags |= WFF_TITLEBAR | WFF_RIGHT_BUTTON;
488 wFrameWindowUpdateBorders(menu->frame, flags);
490 if (menu->flags.titled) {
491 twidth = WMWidthOfString(scr->menu_title_font, menu->frame->title, strlen(menu->frame->title));
492 theight = menu->frame->top_width;
493 twidth += theight + (wPreferences.new_style == TS_NEW ? 16 : 8);
494 } else {
495 twidth = 0;
496 theight = 0;
498 eheight = WMFontHeight(scr->menu_entry_font) + 6 + wPreferences.menu_text_clearance * 2;
499 menu->entry_height = eheight;
500 mrwidth = 0;
501 mwidth = 0;
502 for (i = 0; i < menu->entry_no; i++) {
503 char *text;
505 /* search widest text */
506 text = menu->entries[i]->text;
507 width = WMWidthOfString(scr->menu_entry_font, text, strlen(text)) + 10;
509 if (menu->entries[i]->flags.indicator) {
510 width += MENU_INDICATOR_SPACE;
513 if (width > mwidth)
514 mwidth = width;
516 /* search widest text on right */
517 text = menu->entries[i]->rtext;
518 if (text)
519 rwidth = WMWidthOfString(scr->menu_entry_font, text, strlen(text))
520 + 10;
521 else if (menu->entries[i]->cascade >= 0)
522 rwidth = 16;
523 else
524 rwidth = 4;
526 if (rwidth > mrwidth)
527 mrwidth = rwidth;
529 mwidth += mrwidth;
531 if (mwidth < twidth)
532 mwidth = twidth;
534 wCoreConfigure(menu->menu, 0, theight, mwidth, menu->entry_no * eheight - 1);
536 wFrameWindowResize(menu->frame, mwidth, menu->entry_no * eheight - 1
537 + menu->frame->top_width + menu->frame->bottom_width);
539 updateTexture(menu);
541 menu->flags.realized = 1;
543 if (menu->flags.mapped)
544 wMenuPaint(menu);
545 if (menu->brother->flags.mapped)
546 wMenuPaint(menu->brother);
549 void wMenuDestroy(WMenu * menu, int recurse)
551 int i;
553 WMRemoveNotificationObserver(menu);
555 /* remove any pending timers */
556 if (menu->timer)
557 WMDeleteTimerHandler(menu->timer);
558 menu->timer = NULL;
560 /* call destroy handler */
561 if (menu->on_destroy)
562 (*menu->on_destroy) (menu);
564 /* Destroy items if this menu own them. If this is the "brother" menu,
565 * leave them alone as it is shared by them.
567 if (!menu->flags.brother) {
568 for (i = 0; i < menu->entry_no; i++) {
570 wfree(menu->entries[i]->text);
572 if (menu->entries[i]->rtext)
573 wfree(menu->entries[i]->rtext);
574 #ifdef USER_MENU
576 if (menu->entries[i]->instances) {
577 WMReleasePropList(menu->entries[i]->instances);
579 #endif /* USER_MENU */
581 if (menu->entries[i]->free_cdata && menu->entries[i]->clientdata) {
582 (*menu->entries[i]->free_cdata) (menu->entries[i]->clientdata);
584 wfree(menu->entries[i]);
587 if (recurse) {
588 for (i = 0; i < menu->cascade_no; i++) {
589 if (menu->cascades[i]) {
590 if (menu->cascades[i]->flags.brother)
591 wMenuDestroy(menu->cascades[i]->brother, recurse);
592 else
593 wMenuDestroy(menu->cascades[i], recurse);
598 if (menu->entries)
599 wfree(menu->entries);
603 FREE_PIXMAP(menu->menu_texture_data);
605 if (menu->cascades)
606 wfree(menu->cascades);
608 wCoreDestroy(menu->menu);
609 wFrameWindowDestroy(menu->frame);
611 /* destroy copy of this menu */
612 if (!menu->flags.brother && menu->brother)
613 wMenuDestroy(menu->brother, False);
615 wfree(menu);
618 #define F_NORMAL 0
619 #define F_TOP 1
620 #define F_BOTTOM 2
621 #define F_NONE 3
623 static void drawFrame(WScreen * scr, Drawable win, int y, int w, int h, int type)
625 XSegment segs[2];
626 int i;
628 i = 0;
629 segs[i].x1 = segs[i].x2 = w - 1;
630 segs[i].y1 = y;
631 segs[i].y2 = y + h - 1;
632 i++;
633 if (type != F_TOP && type != F_NONE) {
634 segs[i].x1 = 1;
635 segs[i].y1 = segs[i].y2 = y + h - 2;
636 segs[i].x2 = w - 1;
637 i++;
639 XDrawSegments(dpy, win, scr->menu_item_auxtexture->dim_gc, segs, i);
641 i = 0;
642 segs[i].x1 = 0;
643 segs[i].y1 = y;
644 segs[i].x2 = 0;
645 segs[i].y2 = y + h - 1;
646 i++;
647 if (type != F_BOTTOM && type != F_NONE) {
648 segs[i].x1 = 0;
649 segs[i].y1 = y;
650 segs[i].x2 = w - 1;
651 segs[i].y2 = y;
652 i++;
654 XDrawSegments(dpy, win, scr->menu_item_auxtexture->light_gc, segs, i);
656 if (type != F_TOP && type != F_NONE)
657 XDrawLine(dpy, win, scr->menu_item_auxtexture->dark_gc, 0, y + h - 1, w - 1, y + h - 1);
660 static void paintEntry(WMenu * menu, int index, int selected)
662 WScreen *scr = menu->frame->screen_ptr;
663 Window win = menu->menu->window;
664 WMenuEntry *entry = menu->entries[index];
665 GC light, dim, dark;
666 WMColor *color;
667 int x, y, w, h, tw;
668 int type;
670 if (!menu->flags.realized)
671 return;
672 h = menu->entry_height;
673 w = menu->menu->width;
674 y = index * h;
676 light = scr->menu_item_auxtexture->light_gc;
677 dim = scr->menu_item_auxtexture->dim_gc;
678 dark = scr->menu_item_auxtexture->dark_gc;
680 if (wPreferences.menu_style == MS_FLAT && menu->entry_no > 1) {
681 if (index == 0)
682 type = F_TOP;
683 else if (index == menu->entry_no - 1)
684 type = F_BOTTOM;
685 else
686 type = F_NONE;
687 } else {
688 type = F_NORMAL;
691 /* paint background */
692 if (selected) {
693 XFillRectangle(dpy, win, WMColorGC(scr->select_color), 1, y + 1, w - 2, h - 3);
694 if (scr->menu_item_texture->any.type == WTEX_SOLID)
695 drawFrame(scr, win, y, w, h, type);
696 } else {
697 if (scr->menu_item_texture->any.type == WTEX_SOLID) {
698 XClearArea(dpy, win, 0, y + 1, w - 1, h - 3, False);
699 /* draw the frame */
700 drawFrame(scr, win, y, w, h, type);
701 } else {
702 XClearArea(dpy, win, 0, y, w, h, False);
706 if (selected) {
707 if (entry->flags.enabled)
708 color = scr->select_text_color;
709 else
710 color = scr->dtext_color;
711 } else if (!entry->flags.enabled) {
712 color = scr->dtext_color;
713 } else {
714 color = scr->mtext_color;
716 /* draw text */
717 x = 5;
718 if (entry->flags.indicator)
719 x += MENU_INDICATOR_SPACE + 2;
721 WMDrawString(scr->wmscreen, win, color, scr->menu_entry_font,
722 x, 3 + y + wPreferences.menu_text_clearance, entry->text, strlen(entry->text));
724 if (entry->cascade >= 0) {
725 /* draw the cascade indicator */
726 XDrawLine(dpy, win, dim, w - 11, y + 6, w - 6, y + h / 2 - 1);
727 XDrawLine(dpy, win, light, w - 11, y + h - 8, w - 6, y + h / 2 - 1);
728 XDrawLine(dpy, win, dark, w - 12, y + 6, w - 12, y + h - 8);
731 /* draw indicator */
732 if (entry->flags.indicator && entry->flags.indicator_on) {
733 int iw, ih;
734 WPixmap *indicator;
736 switch (entry->flags.indicator_type) {
737 case MI_CHECK:
738 indicator = scr->menu_check_indicator;
739 break;
740 case MI_MINIWINDOW:
741 indicator = scr->menu_mini_indicator;
742 break;
743 case MI_HIDDEN:
744 indicator = scr->menu_hide_indicator;
745 break;
746 case MI_SHADED:
747 indicator = scr->menu_shade_indicator;
748 break;
749 case MI_DIAMOND:
750 default:
751 indicator = scr->menu_radio_indicator;
752 break;
755 iw = indicator->width;
756 ih = indicator->height;
757 XSetClipMask(dpy, scr->copy_gc, indicator->mask);
758 XSetClipOrigin(dpy, scr->copy_gc, 5, y + (h - ih) / 2);
759 if (selected) {
760 if (entry->flags.enabled) {
761 XSetForeground(dpy, scr->copy_gc, WMColorPixel(scr->select_text_color));
762 } else {
763 XSetForeground(dpy, scr->copy_gc, WMColorPixel(scr->dtext_color));
765 } else {
766 if (entry->flags.enabled) {
767 XSetForeground(dpy, scr->copy_gc, WMColorPixel(scr->mtext_color));
768 } else {
769 XSetForeground(dpy, scr->copy_gc, WMColorPixel(scr->dtext_color));
772 XFillRectangle(dpy, win, scr->copy_gc, 5, y + (h - ih) / 2, iw, ih);
774 XCopyArea(dpy, indicator->image, win, scr->copy_gc, 0, 0,
775 iw, ih, 5, y+(h-ih)/2);
777 XSetClipOrigin(dpy, scr->copy_gc, 0, 0);
780 /* draw right text */
782 if (entry->rtext && entry->cascade < 0) {
783 tw = WMWidthOfString(scr->menu_entry_font, entry->rtext, strlen(entry->rtext));
785 WMDrawString(scr->wmscreen, win, color, scr->menu_entry_font, w - 6 - tw,
786 y + 3 + wPreferences.menu_text_clearance, entry->rtext, strlen(entry->rtext));
790 static void move_menus(WMenu * menu, int x, int y)
792 while (menu->parent) {
793 menu = menu->parent;
794 x -= MENUW(menu);
795 if (!wPreferences.align_menus && menu->selected_entry >= 0) {
796 y -= menu->selected_entry * menu->entry_height;
799 wMenuMove(menu, x, y, True);
802 static void makeVisible(WMenu * menu)
804 WScreen *scr = menu->frame->screen_ptr;
805 int x1, y1, x2, y2, new_x, new_y, move;
806 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
808 if (menu->entry_no < 0)
809 return;
811 x1 = menu->frame_x;
812 y1 = menu->frame_y + menu->frame->top_width + menu->selected_entry * menu->entry_height;
813 x2 = x1 + MENUW(menu);
814 y2 = y1 + menu->entry_height;
816 new_x = x1;
817 new_y = y1;
818 move = 0;
820 if (x1 < rect.pos.x) {
821 new_x = rect.pos.x;
822 move = 1;
823 } else if (x2 >= rect.pos.x + rect.size.width) {
824 new_x = rect.pos.x + rect.size.width - MENUW(menu) - 1;
825 move = 1;
828 if (y1 < rect.pos.y) {
829 new_y = rect.pos.y;
830 move = 1;
831 } else if (y2 >= rect.pos.y + rect.size.height) {
832 new_y = rect.pos.y + rect.size.height - menu->entry_height - 1;
833 move = 1;
836 new_y = new_y - menu->frame->top_width - menu->selected_entry * menu->entry_height;
837 move_menus(menu, new_x, new_y);
840 static int check_key(WMenu * menu, XKeyEvent * event)
842 int i, ch, s;
843 char buffer[32];
845 if (XLookupString(event, buffer, 32, NULL, NULL) < 1)
846 return -1;
848 ch = toupper(buffer[0]);
850 s = (menu->selected_entry >= 0 ? menu->selected_entry + 1 : 0);
852 again:
853 for (i = s; i < menu->entry_no; i++) {
854 if (ch == toupper(menu->entries[i]->text[0])) {
855 return i;
858 /* no match. Retry from start, if previous started from a selected entry */
859 if (s != 0) {
860 s = 0;
861 goto again;
863 return -1;
866 static int keyboardMenu(WMenu * menu)
868 XEvent event;
869 KeySym ksym = NoSymbol;
870 int done = 0;
871 int index;
872 WMenuEntry *entry;
873 int old_pos_x = menu->frame_x;
874 int old_pos_y = menu->frame_y;
875 int new_x = old_pos_x, new_y = old_pos_y;
876 WMRect rect = wGetRectForHead(menu->frame->screen_ptr,
877 wGetHeadForPointerLocation(menu->frame->screen_ptr));
879 if (menu->flags.editing)
880 return False;
882 XGrabKeyboard(dpy, menu->frame->core->window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
884 if (menu->frame_y + menu->frame->top_width >= rect.pos.y + rect.size.height)
885 new_y = rect.pos.y + rect.size.height - menu->frame->top_width;
887 if (menu->frame_x + MENUW(menu) >= rect.pos.x + rect.size.width)
888 new_x = rect.pos.x + rect.size.width - MENUW(menu) - 1;
890 move_menus(menu, new_x, new_y);
892 while (!done && menu->flags.mapped) {
893 XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
894 WMMaskEvent(dpy, ExposureMask | ButtonMotionMask | ButtonPressMask
895 | ButtonReleaseMask | KeyPressMask | KeyReleaseMask | SubstructureNotifyMask, &event);
897 switch (event.type) {
898 case KeyPress:
899 ksym = XLookupKeysym(&event.xkey, 0);
900 switch (ksym) {
901 case XK_Escape:
902 done = 1;
903 break;
905 case XK_Home:
906 #ifdef XK_KP_Home
907 case XK_KP_Home:
908 #endif
909 selectEntry(menu, 0);
910 makeVisible(menu);
911 break;
913 case XK_End:
914 #ifdef XK_KP_End
915 case XK_KP_End:
916 #endif
917 selectEntry(menu, menu->entry_no - 1);
918 makeVisible(menu);
919 break;
921 case XK_Up:
922 #ifdef ARROWLESS_KBD
923 case XK_k:
924 #endif
925 #ifdef XK_KP_Up
926 case XK_KP_Up:
927 #endif
928 if (menu->selected_entry <= 0)
929 selectEntry(menu, menu->entry_no - 1);
930 else
931 selectEntry(menu, menu->selected_entry - 1);
932 makeVisible(menu);
933 break;
935 case XK_Down:
936 #ifdef ARROWLESS_KBD
937 case XK_j:
938 #endif
939 #ifdef XK_KP_Down
940 case XK_KP_Down:
941 #endif
942 if (menu->selected_entry < 0)
943 selectEntry(menu, 0);
944 else if (menu->selected_entry == menu->entry_no - 1)
945 selectEntry(menu, 0);
946 else if (menu->selected_entry < menu->entry_no - 1)
947 selectEntry(menu, menu->selected_entry + 1);
948 makeVisible(menu);
949 break;
951 case XK_Right:
952 #ifdef ARROWLESS_KBD
953 case XK_l:
954 #endif
955 #ifdef XK_KP_Right
956 case XK_KP_Right:
957 #endif
958 if (menu->selected_entry >= 0) {
959 WMenuEntry *entry;
960 entry = menu->entries[menu->selected_entry];
962 if (entry->cascade >= 0 && menu->cascades
963 && menu->cascades[entry->cascade]->entry_no > 0) {
965 XUngrabKeyboard(dpy, CurrentTime);
967 selectEntry(menu->cascades[entry->cascade], 0);
968 if (!keyboardMenu(menu->cascades[entry->cascade]))
969 done = 1;
971 XGrabKeyboard(dpy, menu->frame->core->window, True,
972 GrabModeAsync, GrabModeAsync, CurrentTime);
975 break;
977 case XK_Left:
978 #ifdef ARROWLESS_KBD
979 case XK_h:
980 #endif
981 #ifdef XK_KP_Left
982 case XK_KP_Left:
983 #endif
984 if (menu->parent != NULL && menu->parent->selected_entry >= 0) {
985 selectEntry(menu, -1);
986 move_menus(menu, old_pos_x, old_pos_y);
987 return True;
989 break;
991 case XK_Return:
992 #ifdef XK_KP_Enter
993 case XK_KP_Enter:
994 #endif
995 done = 2;
996 break;
998 default:
999 index = check_key(menu, &event.xkey);
1000 if (index >= 0) {
1001 selectEntry(menu, index);
1004 break;
1006 default:
1007 if (event.type == ButtonPress)
1008 done = 1;
1010 WMHandleEvent(&event);
1014 XUngrabKeyboard(dpy, CurrentTime);
1016 if (done == 2 && menu->selected_entry >= 0) {
1017 entry = menu->entries[menu->selected_entry];
1018 } else {
1019 entry = NULL;
1022 if (entry && entry->callback != NULL && entry->flags.enabled && entry->cascade < 0) {
1023 #if (MENU_BLINK_COUNT > 0)
1024 int sel = menu->selected_entry;
1025 int i;
1027 for (i = 0; i < MENU_BLINK_COUNT; i++) {
1028 paintEntry(menu, sel, False);
1029 XSync(dpy, 0);
1030 wusleep(MENU_BLINK_DELAY);
1031 paintEntry(menu, sel, True);
1032 XSync(dpy, 0);
1033 wusleep(MENU_BLINK_DELAY);
1035 #endif
1036 selectEntry(menu, -1);
1038 if (!menu->flags.buttoned) {
1039 wMenuUnmap(menu);
1040 move_menus(menu, old_pos_x, old_pos_y);
1042 closeCascade(menu);
1044 (*entry->callback) (menu, entry);
1045 } else {
1046 if (!menu->flags.buttoned) {
1047 wMenuUnmap(menu);
1048 move_menus(menu, old_pos_x, old_pos_y);
1050 selectEntry(menu, -1);
1053 /* returns True if returning from a submenu to a parent menu,
1054 * False if exiting from menu */
1055 return False;
1058 void wMenuMapAt(WMenu * menu, int x, int y, int keyboard)
1060 WMRect rect = wGetRectForHead(menu->frame->screen_ptr,
1061 wGetHeadForPointerLocation(menu->frame->screen_ptr));
1063 if (!menu->flags.realized) {
1064 menu->flags.realized = 1;
1065 wMenuRealize(menu);
1067 if (!menu->flags.mapped) {
1068 if (wPreferences.wrap_menus) {
1069 if (x < rect.pos.x)
1070 x = rect.pos.x;
1071 if (y < rect.pos.y)
1072 y = rect.pos.y;
1073 if (x + MENUW(menu) > rect.pos.x + rect.size.width)
1074 x = rect.pos.x + rect.size.width - MENUW(menu);
1075 if (y + MENUH(menu) > rect.pos.y + rect.size.height)
1076 y = rect.pos.y + rect.size.height - MENUH(menu);
1079 XMoveWindow(dpy, menu->frame->core->window, x, y);
1080 menu->frame_x = x;
1081 menu->frame_y = y;
1082 XMapWindow(dpy, menu->frame->core->window);
1083 wRaiseFrame(menu->frame->core);
1084 menu->flags.mapped = 1;
1085 } else {
1086 selectEntry(menu, 0);
1089 if (keyboard)
1090 keyboardMenu(menu);
1093 void wMenuMap(WMenu * menu)
1095 if (!menu->flags.realized) {
1096 menu->flags.realized = 1;
1097 wMenuRealize(menu);
1099 if (menu->flags.app_menu && menu->parent == NULL) {
1100 menu->frame_x = menu->frame->screen_ptr->app_menu_x;
1101 menu->frame_y = menu->frame->screen_ptr->app_menu_y;
1102 XMoveWindow(dpy, menu->frame->core->window, menu->frame_x, menu->frame_y);
1104 XMapWindow(dpy, menu->frame->core->window);
1105 wRaiseFrame(menu->frame->core);
1106 menu->flags.mapped = 1;
1109 void wMenuUnmap(WMenu * menu)
1111 int i;
1113 XUnmapWindow(dpy, menu->frame->core->window);
1114 if (menu->flags.titled && menu->flags.buttoned) {
1115 wFrameWindowHideButton(menu->frame, WFF_RIGHT_BUTTON);
1117 menu->flags.buttoned = 0;
1118 menu->flags.mapped = 0;
1119 menu->flags.open_to_left = 0;
1121 for (i = 0; i < menu->cascade_no; i++) {
1122 if (menu->cascades[i] != NULL
1123 && menu->cascades[i]->flags.mapped && !menu->cascades[i]->flags.buttoned) {
1125 wMenuUnmap(menu->cascades[i]);
1128 menu->selected_entry = -1;
1131 void wMenuPaint(WMenu * menu)
1133 int i;
1135 if (!menu->flags.mapped) {
1136 return;
1139 /* paint entries */
1140 for (i = 0; i < menu->entry_no; i++) {
1141 paintEntry(menu, i, i == menu->selected_entry);
1145 void wMenuSetEnabled(WMenu * menu, int index, int enable)
1147 if (index >= menu->entry_no)
1148 return;
1149 menu->entries[index]->flags.enabled = enable;
1150 paintEntry(menu, index, index == menu->selected_entry);
1151 paintEntry(menu->brother, index, index == menu->selected_entry);
1154 /* ====================================================================== */
1156 static void editEntry(WMenu * menu, WMenuEntry * entry)
1158 WTextInput *text;
1159 XEvent event;
1160 WObjDescriptor *desc;
1161 char *t;
1162 int done = 0;
1163 Window old_focus;
1164 int old_revert;
1166 menu->flags.editing = 1;
1168 text = wTextCreate(menu->menu, 1, menu->entry_height * entry->order,
1169 menu->menu->width - 2, menu->entry_height - 1);
1171 wTextPutText(text, entry->text);
1172 XGetInputFocus(dpy, &old_focus, &old_revert);
1173 XSetInputFocus(dpy, text->core->window, RevertToNone, CurrentTime);
1175 if (XGrabKeyboard(dpy, text->core->window, True, GrabModeAsync, GrabModeAsync, CurrentTime) != GrabSuccess) {
1176 wwarning(_("could not grab keyboard"));
1177 wTextDestroy(text);
1179 wSetFocusTo(menu->frame->screen_ptr, menu->frame->screen_ptr->focused_window);
1180 return;
1183 while (!done && !text->done) {
1184 XSync(dpy, 0);
1185 XAllowEvents(dpy, AsyncKeyboard | AsyncPointer, CurrentTime);
1186 XSync(dpy, 0);
1187 WMNextEvent(dpy, &event);
1189 if (XFindContext(dpy, event.xany.window, wWinContext, (XPointer *) & desc) == XCNOENT)
1190 desc = NULL;
1192 if ((desc != NULL) && (desc->handle_anything != NULL)) {
1194 (*desc->handle_anything) (desc, &event);
1196 } else {
1197 switch (event.type) {
1198 case ButtonPress:
1199 XAllowEvents(dpy, ReplayPointer, CurrentTime);
1200 done = 1;
1202 default:
1203 WMHandleEvent(&event);
1204 break;
1209 XSetInputFocus(dpy, old_focus, old_revert, CurrentTime);
1211 wSetFocusTo(menu->frame->screen_ptr, menu->frame->screen_ptr->focused_window);
1213 t = wTextGetText(text);
1214 /* if !t, the user has canceled editing */
1215 if (t) {
1216 if (entry->text)
1217 wfree(entry->text);
1218 entry->text = wstrdup(t);
1220 menu->flags.realized = 0;
1222 wTextDestroy(text);
1224 XUngrabKeyboard(dpy, CurrentTime);
1226 if (t && menu->on_edit)
1227 (*menu->on_edit) (menu, entry);
1229 menu->flags.editing = 0;
1231 if (!menu->flags.realized)
1232 wMenuRealize(menu);
1235 static void selectEntry(WMenu * menu, int entry_no)
1237 WMenuEntry *entry;
1238 WMenu *submenu;
1239 int old_entry;
1241 if (menu->entries == NULL)
1242 return;
1244 if (entry_no >= menu->entry_no)
1245 return;
1247 old_entry = menu->selected_entry;
1248 menu->selected_entry = entry_no;
1250 if (old_entry != entry_no) {
1252 /* unselect previous entry */
1253 if (old_entry >= 0) {
1254 paintEntry(menu, old_entry, False);
1255 entry = menu->entries[old_entry];
1257 /* unmap cascade */
1258 if (entry->cascade >= 0 && menu->cascades) {
1259 if (!menu->cascades[entry->cascade]->flags.buttoned) {
1260 wMenuUnmap(menu->cascades[entry->cascade]);
1265 if (entry_no < 0) {
1266 menu->selected_entry = -1;
1267 return;
1269 entry = menu->entries[entry_no];
1271 if (entry->cascade >= 0 && menu->cascades && entry->flags.enabled) {
1272 /* Callback for when the submenu is opened.
1274 submenu = menu->cascades[entry->cascade];
1275 if (submenu && submenu->flags.brother)
1276 submenu = submenu->brother;
1278 if (entry->callback) {
1279 /* Only call the callback if the submenu is not yet mapped.
1281 if (menu->flags.brother) {
1282 if (!submenu || !submenu->flags.mapped)
1283 (*entry->callback) (menu->brother, entry);
1284 } else {
1285 if (!submenu || !submenu->flags.buttoned)
1286 (*entry->callback) (menu, entry);
1290 /* the submenu menu might have changed */
1291 submenu = menu->cascades[entry->cascade];
1293 /* map cascade */
1294 if (!submenu->flags.mapped) {
1295 int x, y;
1297 if (!submenu->flags.realized)
1298 wMenuRealize(submenu);
1299 if (wPreferences.wrap_menus) {
1300 if (menu->flags.open_to_left)
1301 submenu->flags.open_to_left = 1;
1303 if (submenu->flags.open_to_left) {
1304 x = menu->frame_x - MENUW(submenu);
1305 if (x < 0) {
1306 x = 0;
1307 submenu->flags.open_to_left = 0;
1309 } else {
1310 x = menu->frame_x + MENUW(menu);
1312 if (x + MENUW(submenu)
1313 >= menu->frame->screen_ptr->scr_width) {
1315 x = menu->frame_x - MENUW(submenu);
1316 submenu->flags.open_to_left = 1;
1319 } else {
1320 x = menu->frame_x + MENUW(menu);
1323 if (wPreferences.align_menus) {
1324 y = menu->frame_y;
1325 } else {
1326 y = menu->frame_y + menu->entry_height * entry_no;
1327 if (menu->flags.titled)
1328 y += menu->frame->top_width;
1329 if (menu->cascades[entry->cascade]->flags.titled)
1330 y -= menu->cascades[entry->cascade]->frame->top_width;
1333 wMenuMapAt(menu->cascades[entry->cascade], x, y, False);
1334 menu->cascades[entry->cascade]->parent = menu;
1335 } else {
1336 return;
1339 paintEntry(menu, entry_no, True);
1343 static WMenu *findMenu(WScreen * scr, int *x_ret, int *y_ret)
1345 WMenu *menu;
1346 WObjDescriptor *desc;
1347 Window root_ret, win, junk_win;
1348 int x, y, wx, wy;
1349 unsigned int mask;
1351 XQueryPointer(dpy, scr->root_win, &root_ret, &win, &x, &y, &wx, &wy, &mask);
1353 if (win == None)
1354 return NULL;
1356 if (XFindContext(dpy, win, wWinContext, (XPointer *) & desc) == XCNOENT)
1357 return NULL;
1359 if (desc->parent_type == WCLASS_MENU) {
1360 menu = (WMenu *) desc->parent;
1361 XTranslateCoordinates(dpy, root_ret, menu->menu->window, wx, wy, x_ret, y_ret, &junk_win);
1362 return menu;
1364 return NULL;
1367 static void closeCascade(WMenu * menu)
1369 WMenu *parent = menu->parent;
1371 if (menu->flags.brother || (!menu->flags.buttoned && (!menu->flags.app_menu || menu->parent != NULL))) {
1373 selectEntry(menu, -1);
1374 XSync(dpy, 0);
1375 #if (MENU_BLINK_DELAY > 2)
1376 wusleep(MENU_BLINK_DELAY / 2);
1377 #endif
1378 wMenuUnmap(menu);
1379 while (parent != NULL
1380 && (parent->parent != NULL || !parent->flags.app_menu || parent->flags.brother)
1381 && !parent->flags.buttoned) {
1382 selectEntry(parent, -1);
1383 wMenuUnmap(parent);
1384 parent = parent->parent;
1386 if (parent)
1387 selectEntry(parent, -1);
1391 static void closeBrotherCascadesOf(WMenu * menu)
1393 WMenu *tmp;
1394 int i;
1396 for (i = 0; i < menu->cascade_no; i++) {
1397 if (menu->cascades[i]->flags.brother) {
1398 tmp = menu->cascades[i];
1399 } else {
1400 tmp = menu->cascades[i]->brother;
1402 if (tmp->flags.mapped) {
1403 selectEntry(tmp->parent, -1);
1404 closeBrotherCascadesOf(tmp);
1405 break;
1410 #define getEntryAt(menu, x, y) ((y)<0 ? -1 : (y)/(menu->entry_height))
1412 static WMenu *parentMenu(WMenu * menu)
1414 WMenu *parent;
1415 WMenuEntry *entry;
1417 if (menu->flags.buttoned)
1418 return menu;
1420 while (menu->parent && menu->parent->flags.mapped) {
1421 parent = menu->parent;
1422 if (parent->selected_entry < 0)
1423 break;
1424 entry = parent->entries[parent->selected_entry];
1425 if (!entry->flags.enabled || entry->cascade < 0 || !parent->cascades ||
1426 parent->cascades[entry->cascade] != menu)
1427 break;
1428 menu = parent;
1429 if (menu->flags.buttoned)
1430 break;
1433 return menu;
1437 * Will raise the passed menu, if submenu = 0
1438 * If submenu > 0 will also raise all mapped submenus
1439 * until the first buttoned one
1440 * If submenu < 0 will also raise all mapped parent menus
1441 * until the first buttoned one
1444 static void raiseMenus(WMenu * menu, int submenus)
1446 WMenu *submenu;
1447 int i;
1449 if (!menu)
1450 return;
1452 wRaiseFrame(menu->frame->core);
1454 if (submenus > 0 && menu->selected_entry >= 0) {
1455 i = menu->entries[menu->selected_entry]->cascade;
1456 if (i >= 0 && menu->cascades) {
1457 submenu = menu->cascades[i];
1458 if (submenu->flags.mapped && !submenu->flags.buttoned)
1459 raiseMenus(submenu, submenus);
1462 if (submenus < 0 && !menu->flags.buttoned && menu->parent && menu->parent->flags.mapped)
1463 raiseMenus(menu->parent, submenus);
1466 WMenu *wMenuUnderPointer(WScreen * screen)
1468 WObjDescriptor *desc;
1469 Window root_ret, win;
1470 int dummy;
1471 unsigned int mask;
1473 XQueryPointer(dpy, screen->root_win, &root_ret, &win, &dummy, &dummy, &dummy, &dummy, &mask);
1475 if (win == None)
1476 return NULL;
1478 if (XFindContext(dpy, win, wWinContext, (XPointer *) & desc) == XCNOENT)
1479 return NULL;
1481 if (desc->parent_type == WCLASS_MENU)
1482 return (WMenu *) desc->parent;
1483 return NULL;
1486 static void getPointerPosition(WScreen * scr, int *x, int *y)
1488 Window root_ret, win;
1489 int wx, wy;
1490 unsigned int mask;
1492 XQueryPointer(dpy, scr->root_win, &root_ret, &win, x, y, &wx, &wy, &mask);
1495 static void getScrollAmount(WMenu * menu, int *hamount, int *vamount)
1497 WScreen *scr = menu->menu->screen_ptr;
1498 int menuX1 = menu->frame_x;
1499 int menuY1 = menu->frame_y;
1500 int menuX2 = menu->frame_x + MENUW(menu);
1501 int menuY2 = menu->frame_y + MENUH(menu);
1502 int xroot, yroot;
1503 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
1505 *hamount = 0;
1506 *vamount = 0;
1508 getPointerPosition(scr, &xroot, &yroot);
1510 if (xroot <= (rect.pos.x + 1) && menuX1 < rect.pos.x) {
1511 /* scroll to the right */
1512 *hamount = WMIN(MENU_SCROLL_STEP, abs(menuX1));
1514 } else if (xroot >= (rect.pos.x + rect.size.width - 2) && menuX2 > (rect.pos.x + rect.size.width - 1)) {
1515 /* scroll to the left */
1516 *hamount = WMIN(MENU_SCROLL_STEP, abs(menuX2 - rect.pos.x - rect.size.width - 1));
1518 if (*hamount == 0)
1519 *hamount = 1;
1521 *hamount = -*hamount;
1524 if (yroot <= (rect.pos.y + 1) && menuY1 < rect.pos.y) {
1525 /* scroll down */
1526 *vamount = WMIN(MENU_SCROLL_STEP, abs(menuY1));
1528 } else if (yroot >= (rect.pos.y + rect.size.height - 2) && menuY2 > (rect.pos.y + rect.size.height - 1)) {
1529 /* scroll up */
1530 *vamount = WMIN(MENU_SCROLL_STEP, abs(menuY2 - rect.pos.y - rect.size.height - 2));
1532 *vamount = -*vamount;
1536 static void dragScrollMenuCallback(void *data)
1538 WMenu *menu = (WMenu *) data;
1539 WScreen *scr = menu->menu->screen_ptr;
1540 WMenu *parent = parentMenu(menu);
1541 int hamount, vamount;
1542 int x, y;
1543 int newSelectedEntry;
1545 getScrollAmount(menu, &hamount, &vamount);
1547 if (hamount != 0 || vamount != 0) {
1548 wMenuMove(parent, parent->frame_x + hamount, parent->frame_y + vamount, True);
1549 if (findMenu(scr, &x, &y)) {
1550 newSelectedEntry = getEntryAt(menu, x, y);
1551 selectEntry(menu, newSelectedEntry);
1552 } else {
1553 /* Pointer fell outside of menu. If the selected entry is
1554 * not a submenu, unselect it */
1555 if (menu->selected_entry >= 0 && menu->entries[menu->selected_entry]->cascade < 0)
1556 selectEntry(menu, -1);
1557 newSelectedEntry = 0;
1560 /* paranoid check */
1561 if (newSelectedEntry >= 0) {
1562 /* keep scrolling */
1563 menu->timer = WMAddTimerHandler(MENU_SCROLL_DELAY, dragScrollMenuCallback, menu);
1564 } else {
1565 menu->timer = NULL;
1567 } else {
1568 /* don't need to scroll anymore */
1569 menu->timer = NULL;
1570 if (findMenu(scr, &x, &y)) {
1571 newSelectedEntry = getEntryAt(menu, x, y);
1572 selectEntry(menu, newSelectedEntry);
1577 static void scrollMenuCallback(void *data)
1579 WMenu *menu = (WMenu *) data;
1580 WMenu *parent = parentMenu(menu);
1581 int hamount = 0; /* amount to scroll */
1582 int vamount = 0;
1584 getScrollAmount(menu, &hamount, &vamount);
1586 if (hamount != 0 || vamount != 0) {
1587 wMenuMove(parent, parent->frame_x + hamount, parent->frame_y + vamount, True);
1589 /* keep scrolling */
1590 menu->timer = WMAddTimerHandler(MENU_SCROLL_DELAY, scrollMenuCallback, menu);
1591 } else {
1592 /* don't need to scroll anymore */
1593 menu->timer = NULL;
1597 #define MENU_SCROLL_BORDER 5
1599 static int isPointNearBoder(WMenu * menu, int x, int y)
1601 int menuX1 = menu->frame_x;
1602 int menuY1 = menu->frame_y;
1603 int menuX2 = menu->frame_x + MENUW(menu);
1604 int menuY2 = menu->frame_y + MENUH(menu);
1605 int flag = 0;
1606 int head = wGetHeadForPoint(menu->frame->screen_ptr, wmkpoint(x, y));
1607 WMRect rect = wGetRectForHead(menu->frame->screen_ptr, head);
1609 /* XXX: handle screen joins properly !! */
1611 if (x >= menuX1 && x <= menuX2 &&
1612 (y < rect.pos.y + MENU_SCROLL_BORDER || y >= rect.pos.y + rect.size.height - MENU_SCROLL_BORDER))
1613 flag = 1;
1614 else if (y >= menuY1 && y <= menuY2 &&
1615 (x < rect.pos.x + MENU_SCROLL_BORDER || x >= rect.pos.x + rect.size.width - MENU_SCROLL_BORDER))
1616 flag = 1;
1618 return flag;
1621 typedef struct _delay {
1622 WMenu *menu;
1623 int ox, oy;
1624 } _delay;
1626 static void leaving(_delay * dl)
1628 wMenuMove(dl->menu, dl->ox, dl->oy, True);
1629 dl->menu->jump_back = NULL;
1630 dl->menu->menu->screen_ptr->flags.jump_back_pending = 0;
1631 wfree(dl);
1634 void wMenuScroll(WMenu * menu, XEvent * event)
1636 WMenu *smenu;
1637 WMenu *omenu = parentMenu(menu);
1638 WScreen *scr = menu->frame->screen_ptr;
1639 int done = 0;
1640 int jump_back = 0;
1641 int old_frame_x = omenu->frame_x;
1642 int old_frame_y = omenu->frame_y;
1643 XEvent ev;
1645 if (omenu->jump_back)
1646 WMDeleteTimerWithClientData(omenu->jump_back);
1648 if (( /*omenu->flags.buttoned && */ !wPreferences.wrap_menus)
1649 || omenu->flags.app_menu) {
1650 jump_back = 1;
1653 if (!wPreferences.wrap_menus)
1654 raiseMenus(omenu, True);
1655 else
1656 raiseMenus(menu, False);
1658 if (!menu->timer)
1659 scrollMenuCallback(menu);
1661 while (!done) {
1662 int x, y, on_border, on_x_edge, on_y_edge, on_title;
1663 WMRect rect;
1665 WMNextEvent(dpy, &ev);
1666 switch (ev.type) {
1667 case EnterNotify:
1668 WMHandleEvent(&ev);
1669 case MotionNotify:
1670 x = (ev.type == MotionNotify) ? ev.xmotion.x_root : ev.xcrossing.x_root;
1671 y = (ev.type == MotionNotify) ? ev.xmotion.y_root : ev.xcrossing.y_root;
1673 /* on_border is != 0 if the pointer is between the menu
1674 * and the screen border and is close enough to the border */
1675 on_border = isPointNearBoder(menu, x, y);
1677 smenu = wMenuUnderPointer(scr);
1679 if ((smenu == NULL && !on_border) || (smenu && parentMenu(smenu) != omenu)) {
1680 done = 1;
1681 break;
1684 rect = wGetRectForHead(scr, wGetHeadForPoint(scr, wmkpoint(x, y)));
1685 on_x_edge = x <= rect.pos.x + 1 || x >= rect.pos.x + rect.size.width - 2;
1686 on_y_edge = y <= rect.pos.y + 1 || y >= rect.pos.y + rect.size.height - 2;
1687 on_border = on_x_edge || on_y_edge;
1689 if (!on_border && !jump_back) {
1690 done = 1;
1691 break;
1694 if (menu->timer && (smenu != menu || (!on_y_edge && !on_x_edge))) {
1695 WMDeleteTimerHandler(menu->timer);
1696 menu->timer = NULL;
1699 if (smenu != NULL)
1700 menu = smenu;
1702 if (!menu->timer)
1703 scrollMenuCallback(menu);
1704 break;
1705 case ButtonPress:
1706 /* True if we push on title, or drag the omenu to other position */
1707 on_title = ev.xbutton.x_root >= omenu->frame_x &&
1708 ev.xbutton.x_root <= omenu->frame_x + MENUW(omenu) &&
1709 ev.xbutton.y_root >= omenu->frame_y &&
1710 ev.xbutton.y_root <= omenu->frame_y + omenu->frame->top_width;
1711 WMHandleEvent(&ev);
1712 smenu = wMenuUnderPointer(scr);
1713 if (smenu == NULL || (smenu && smenu->flags.buttoned && smenu != omenu))
1714 done = 1;
1715 else if (smenu == omenu && on_title) {
1716 jump_back = 0;
1717 done = 1;
1719 break;
1720 case KeyPress:
1721 done = 1;
1722 default:
1723 WMHandleEvent(&ev);
1724 break;
1728 if (menu->timer) {
1729 WMDeleteTimerHandler(menu->timer);
1730 menu->timer = NULL;
1733 if (jump_back) {
1734 _delay *delayer;
1735 if (!omenu->jump_back) {
1736 delayer = wmalloc(sizeof(_delay));
1737 delayer->menu = omenu;
1738 delayer->ox = old_frame_x;
1739 delayer->oy = old_frame_y;
1740 omenu->jump_back = delayer;
1741 scr->flags.jump_back_pending = 1;
1742 } else
1743 delayer = omenu->jump_back;
1744 WMAddTimerHandler(MENU_JUMP_BACK_DELAY, (WMCallback *) leaving, delayer);
1748 static void menuExpose(WObjDescriptor * desc, XEvent * event)
1750 wMenuPaint(desc->parent);
1753 typedef struct {
1754 int *delayed_select;
1755 WMenu *menu;
1756 WMHandlerID magic;
1757 } delay_data;
1759 static void delaySelection(void *data)
1761 delay_data *d = (delay_data *) data;
1762 int x, y, entry_no;
1763 WMenu *menu;
1765 d->magic = NULL;
1767 menu = findMenu(d->menu->menu->screen_ptr, &x, &y);
1768 if (menu && (d->menu == menu || d->delayed_select)) {
1769 entry_no = getEntryAt(menu, x, y);
1770 selectEntry(menu, entry_no);
1772 if (d->delayed_select)
1773 *(d->delayed_select) = 0;
1776 static void menuMouseDown(WObjDescriptor * desc, XEvent * event)
1778 WWindow *wwin;
1779 XButtonEvent *bev = &event->xbutton;
1780 WMenu *menu = desc->parent;
1781 WMenu *smenu;
1782 WScreen *scr = menu->frame->screen_ptr;
1783 WMenuEntry *entry = NULL;
1784 XEvent ev;
1785 int close_on_exit = 0;
1786 int done = 0;
1787 int delayed_select = 0;
1788 int entry_no;
1789 int x, y;
1790 int prevx, prevy;
1791 int old_frame_x = 0;
1792 int old_frame_y = 0;
1793 delay_data d_data = { NULL, NULL, NULL };
1795 /* Doesn't seem to be needed anymore (if delayed selection handler is
1796 * added only if not present). there seem to be no other side effects
1797 * from removing this and it is also possible that it was only added
1798 * to avoid problems with adding the delayed selection timer handler
1799 * multiple times
1801 /*if (menu->flags.inside_handler) {
1802 return;
1803 } */
1804 menu->flags.inside_handler = 1;
1806 if (!wPreferences.wrap_menus) {
1807 smenu = parentMenu(menu);
1808 old_frame_x = smenu->frame_x;
1809 old_frame_y = smenu->frame_y;
1810 } else if (event->xbutton.window == menu->frame->core->window) {
1811 /* This is true if the menu was launched with right click on root window */
1812 if (!d_data.magic) {
1813 delayed_select = 1;
1814 d_data.delayed_select = &delayed_select;
1815 d_data.menu = menu;
1816 d_data.magic = WMAddTimerHandler(wPreferences.dblclick_time, delaySelection, &d_data);
1820 wRaiseFrame(menu->frame->core);
1822 close_on_exit = (bev->send_event || menu->flags.brother);
1824 smenu = findMenu(scr, &x, &y);
1825 if (!smenu) {
1826 x = -1;
1827 y = -1;
1828 } else {
1829 menu = smenu;
1832 if (menu->flags.editing) {
1833 goto byebye;
1835 entry_no = getEntryAt(menu, x, y);
1836 if (entry_no >= 0) {
1837 entry = menu->entries[entry_no];
1839 if (!close_on_exit && (bev->state & ControlMask) && smenu && entry->flags.editable) {
1840 editEntry(smenu, entry);
1841 goto byebye;
1842 } else if (bev->state & ControlMask) {
1843 goto byebye;
1846 if (entry->flags.enabled && entry->cascade >= 0 && menu->cascades) {
1847 WMenu *submenu = menu->cascades[entry->cascade];
1848 /* map cascade */
1849 if (submenu->flags.mapped && !submenu->flags.buttoned && menu->selected_entry != entry_no) {
1850 wMenuUnmap(submenu);
1852 if (!submenu->flags.mapped && !delayed_select) {
1853 selectEntry(menu, entry_no);
1854 } else if (!submenu->flags.buttoned) {
1855 selectEntry(menu, -1);
1858 } else if (!delayed_select) {
1859 if (menu == scr->switch_menu && event->xbutton.button == Button3) {
1860 selectEntry(menu, entry_no);
1861 OpenWindowMenu2((WWindow *)entry->clientdata,
1862 event->xbutton.x_root,
1863 event->xbutton.y_root, False);
1864 wwin = (WWindow *)entry->clientdata;
1865 desc = &wwin->screen_ptr->window_menu->menu->descriptor;
1866 event->xany.send_event = True;
1867 (*desc->handle_mousedown)(desc, event);
1869 XUngrabPointer(dpy, CurrentTime);
1870 selectEntry(menu, -1);
1871 return;
1872 } else {
1873 selectEntry(menu, entry_no);
1877 if (!wPreferences.wrap_menus && !wPreferences.scrollable_menus) {
1878 if (!menu->timer)
1879 dragScrollMenuCallback(menu);
1883 prevx = bev->x_root;
1884 prevy = bev->y_root;
1885 while (!done) {
1886 int x, y;
1888 XAllowEvents(dpy, AsyncPointer | SyncPointer, CurrentTime);
1890 WMMaskEvent(dpy, ExposureMask | ButtonMotionMask | ButtonReleaseMask | ButtonPressMask, &ev);
1891 switch (ev.type) {
1892 case MotionNotify:
1893 smenu = findMenu(scr, &x, &y);
1895 if (smenu == NULL) {
1896 /* moved mouse out of menu */
1898 if (!delayed_select && d_data.magic) {
1899 WMDeleteTimerHandler(d_data.magic);
1900 d_data.magic = NULL;
1902 if (menu == NULL
1903 || (menu->selected_entry >= 0
1904 && menu->entries[menu->selected_entry]->cascade >= 0)) {
1905 prevx = ev.xmotion.x_root;
1906 prevy = ev.xmotion.y_root;
1908 break;
1910 selectEntry(menu, -1);
1911 menu = smenu;
1912 prevx = ev.xmotion.x_root;
1913 prevy = ev.xmotion.y_root;
1914 break;
1915 } else if (menu && menu != smenu
1916 && (menu->selected_entry < 0
1917 || menu->entries[menu->selected_entry]->cascade < 0)) {
1918 selectEntry(menu, -1);
1920 if (!delayed_select && d_data.magic) {
1921 WMDeleteTimerHandler(d_data.magic);
1922 d_data.magic = NULL;
1924 } else {
1926 /* hysteresis for item selection */
1928 /* check if the motion was to the side, indicating that
1929 * the user may want to cross to a submenu */
1930 if (!delayed_select && menu) {
1931 int dx;
1932 Bool moved_to_submenu; /* moved to direction of submenu */
1934 dx = abs(prevx - ev.xmotion.x_root);
1936 moved_to_submenu = False;
1937 if (dx > 0 /* if moved enough to the side */
1938 /* maybe a open submenu */
1939 && menu->selected_entry >= 0
1940 /* moving to the right direction */
1941 && (wPreferences.align_menus || ev.xmotion.y_root >= prevy)) {
1942 int index;
1944 index = menu->entries[menu->selected_entry]->cascade;
1945 if (index >= 0) {
1946 if (menu->cascades[index]->frame_x > menu->frame_x) {
1947 if (prevx < ev.xmotion.x_root)
1948 moved_to_submenu = True;
1949 } else {
1950 if (prevx > ev.xmotion.x_root)
1951 moved_to_submenu = True;
1956 if (menu != smenu) {
1957 if (d_data.magic) {
1958 WMDeleteTimerHandler(d_data.magic);
1959 d_data.magic = NULL;
1961 } else if (moved_to_submenu) {
1962 /* while we are moving, postpone the selection */
1963 if (d_data.magic) {
1964 WMDeleteTimerHandler(d_data.magic);
1966 d_data.delayed_select = NULL;
1967 d_data.menu = menu;
1968 d_data.magic = WMAddTimerHandler(MENU_SELECT_DELAY,
1969 delaySelection, &d_data);
1970 prevx = ev.xmotion.x_root;
1971 prevy = ev.xmotion.y_root;
1972 break;
1973 } else {
1974 if (d_data.magic) {
1975 WMDeleteTimerHandler(d_data.magic);
1976 d_data.magic = NULL;
1981 prevx = ev.xmotion.x_root;
1982 prevy = ev.xmotion.y_root;
1983 if (menu != smenu) {
1984 /* pointer crossed menus */
1985 if (menu && menu->timer) {
1986 WMDeleteTimerHandler(menu->timer);
1987 menu->timer = NULL;
1989 if (smenu)
1990 dragScrollMenuCallback(smenu);
1992 menu = smenu;
1993 if (!menu->timer)
1994 dragScrollMenuCallback(menu);
1996 if (!delayed_select) {
1997 entry_no = getEntryAt(menu, x, y);
1998 if (entry_no >= 0) {
1999 entry = menu->entries[entry_no];
2000 if (entry->flags.enabled && entry->cascade >= 0 && menu->cascades) {
2001 WMenu *submenu = menu->cascades[entry->cascade];
2002 if (submenu->flags.mapped && !submenu->flags.buttoned
2003 && menu->selected_entry != entry_no) {
2004 wMenuUnmap(submenu);
2008 selectEntry(menu, entry_no);
2010 break;
2012 case ButtonPress:
2013 break;
2015 case ButtonRelease:
2016 if (ev.xbutton.button == event->xbutton.button)
2017 done = 1;
2018 break;
2020 case Expose:
2021 WMHandleEvent(&ev);
2022 break;
2026 if (menu && menu->timer) {
2027 WMDeleteTimerHandler(menu->timer);
2028 menu->timer = NULL;
2030 if (d_data.magic != NULL) {
2031 WMDeleteTimerHandler(d_data.magic);
2032 d_data.magic = NULL;
2035 if (menu && menu->selected_entry >= 0) {
2036 entry = menu->entries[menu->selected_entry];
2037 if (entry->callback != NULL && entry->flags.enabled && entry->cascade < 0) {
2038 /* blink and erase menu selection */
2039 #if (MENU_BLINK_DELAY > 0)
2040 int sel = menu->selected_entry;
2041 int i;
2043 for (i = 0; i < MENU_BLINK_COUNT; i++) {
2044 paintEntry(menu, sel, False);
2045 XSync(dpy, 0);
2046 wusleep(MENU_BLINK_DELAY);
2047 paintEntry(menu, sel, True);
2048 XSync(dpy, 0);
2049 wusleep(MENU_BLINK_DELAY);
2051 #endif
2052 /* unmap the menu, it's parents and call the callback */
2053 if (!menu->flags.buttoned && (!menu->flags.app_menu || menu->parent != NULL)) {
2054 closeCascade(menu);
2055 } else {
2056 selectEntry(menu, -1);
2058 (*entry->callback) (menu, entry);
2060 /* If the user double clicks an entry, the entry will
2061 * be executed twice, which is not good for things like
2062 * the root menu. So, ignore any clicks that were generated
2063 * while the entry was being executed */
2064 while (XCheckTypedWindowEvent(dpy, menu->menu->window, ButtonPress, &ev)) ;
2065 } else if (entry->callback != NULL && entry->cascade < 0) {
2066 selectEntry(menu, -1);
2067 } else {
2068 if (entry->cascade >= 0 && menu->cascades && menu->cascades[entry->cascade]->flags.brother) {
2069 selectEntry(menu, -1);
2074 if (((WMenu *) desc->parent)->flags.brother || close_on_exit || !smenu)
2075 closeCascade(desc->parent);
2077 /* close the cascade windows that should not remain opened */
2078 closeBrotherCascadesOf(desc->parent);
2080 if (!wPreferences.wrap_menus)
2081 wMenuMove(parentMenu(desc->parent), old_frame_x, old_frame_y, True);
2083 byebye:
2084 /* Just to be sure in case we skip the 2 above because of a goto byebye */
2085 if (menu && menu->timer) {
2086 WMDeleteTimerHandler(menu->timer);
2087 menu->timer = NULL;
2089 if (d_data.magic != NULL) {
2090 WMDeleteTimerHandler(d_data.magic);
2091 d_data.magic = NULL;
2094 ((WMenu *) desc->parent)->flags.inside_handler = 0;
2097 void wMenuMove(WMenu * menu, int x, int y, int submenus)
2099 WMenu *submenu;
2100 int i;
2102 if (!menu)
2103 return;
2105 menu->frame_x = x;
2106 menu->frame_y = y;
2107 XMoveWindow(dpy, menu->frame->core->window, x, y);
2109 if (submenus > 0 && menu->selected_entry >= 0) {
2110 i = menu->entries[menu->selected_entry]->cascade;
2112 if (i >= 0 && menu->cascades) {
2113 submenu = menu->cascades[i];
2114 if (submenu->flags.mapped && !submenu->flags.buttoned) {
2115 if (wPreferences.align_menus) {
2116 wMenuMove(submenu, x + MENUW(menu), y, submenus);
2117 } else {
2118 wMenuMove(submenu, x + MENUW(menu),
2119 y + submenu->entry_height * menu->selected_entry, submenus);
2124 if (submenus < 0 && menu->parent != NULL && menu->parent->flags.mapped && !menu->parent->flags.buttoned) {
2125 if (wPreferences.align_menus) {
2126 wMenuMove(menu->parent, x - MENUW(menu->parent), y, submenus);
2127 } else {
2128 wMenuMove(menu->parent, x - MENUW(menu->parent), menu->frame_y
2129 - menu->parent->entry_height * menu->parent->selected_entry, submenus);
2134 static void changeMenuLevels(WMenu * menu, int lower)
2136 int i;
2138 if (!lower) {
2139 ChangeStackingLevel(menu->frame->core, (!menu->parent ? WMMainMenuLevel : WMSubmenuLevel));
2140 wRaiseFrame(menu->frame->core);
2141 menu->flags.lowered = 0;
2142 } else {
2143 ChangeStackingLevel(menu->frame->core, WMNormalLevel);
2144 wLowerFrame(menu->frame->core);
2145 menu->flags.lowered = 1;
2147 for (i = 0; i < menu->cascade_no; i++) {
2148 if (menu->cascades[i]
2149 && !menu->cascades[i]->flags.buttoned && menu->cascades[i]->flags.lowered != lower) {
2150 changeMenuLevels(menu->cascades[i], lower);
2155 static void menuTitleDoubleClick(WCoreWindow * sender, void *data, XEvent * event)
2157 WMenu *menu = data;
2158 int lower;
2160 if (event->xbutton.state & MOD_MASK) {
2161 if (menu->flags.lowered) {
2162 lower = 0;
2163 } else {
2164 lower = 1;
2166 changeMenuLevels(menu, lower);
2170 static void menuTitleMouseDown(WCoreWindow * sender, void *data, XEvent * event)
2172 WMenu *menu = data;
2173 WMenu *tmp;
2174 XEvent ev;
2175 int x = menu->frame_x, y = menu->frame_y;
2176 int dx = event->xbutton.x_root, dy = event->xbutton.y_root;
2177 int i, lower;
2178 Bool started;
2180 /* can't touch the menu copy */
2181 if (menu->flags.brother)
2182 return;
2184 if (event->xbutton.button != Button1 && event->xbutton.button != Button2)
2185 return;
2187 if (event->xbutton.state & MOD_MASK) {
2188 wLowerFrame(menu->frame->core);
2189 lower = 1;
2190 } else {
2191 wRaiseFrame(menu->frame->core);
2192 lower = 0;
2194 tmp = menu;
2196 /* lower/raise all submenus */
2197 while (1) {
2198 if (tmp->selected_entry >= 0 && tmp->cascades && tmp->entries[tmp->selected_entry]->cascade >= 0) {
2199 tmp = tmp->cascades[tmp->entries[tmp->selected_entry]->cascade];
2200 if (!tmp || !tmp->flags.mapped)
2201 break;
2202 if (lower)
2203 wLowerFrame(tmp->frame->core);
2204 else
2205 wRaiseFrame(tmp->frame->core);
2206 } else {
2207 break;
2211 /* tear off the menu if it's a root menu or a cascade
2212 application menu */
2213 if (!menu->flags.buttoned && !menu->flags.brother && (!menu->flags.app_menu || menu->parent != NULL)) {
2214 menu->flags.buttoned = 1;
2215 wFrameWindowShowButton(menu->frame, WFF_RIGHT_BUTTON);
2216 if (menu->parent) {
2217 /* turn off selected menu entry in parent menu */
2218 selectEntry(menu->parent, -1);
2220 /* make parent map the copy in place of the original */
2221 for (i = 0; i < menu->parent->cascade_no; i++) {
2222 if (menu->parent->cascades[i] == menu) {
2223 menu->parent->cascades[i] = menu->brother;
2224 break;
2230 started = False;
2231 while (1) {
2232 WMMaskEvent(dpy, ButtonMotionMask | ButtonReleaseMask | ButtonPressMask | ExposureMask, &ev);
2233 switch (ev.type) {
2234 case MotionNotify:
2235 if (started) {
2236 x += ev.xmotion.x_root - dx;
2237 y += ev.xmotion.y_root - dy;
2238 dx = ev.xmotion.x_root;
2239 dy = ev.xmotion.y_root;
2240 wMenuMove(menu, x, y, True);
2241 } else {
2242 if (abs(ev.xmotion.x_root - dx) > MOVE_THRESHOLD
2243 || abs(ev.xmotion.y_root - dy) > MOVE_THRESHOLD) {
2244 started = True;
2245 XGrabPointer(dpy, menu->frame->titlebar->window, False,
2246 ButtonMotionMask | ButtonReleaseMask
2247 | ButtonPressMask,
2248 GrabModeAsync, GrabModeAsync, None,
2249 wCursor[WCUR_MOVE], CurrentTime);
2252 break;
2254 case ButtonPress:
2255 break;
2257 case ButtonRelease:
2258 if (ev.xbutton.button != event->xbutton.button)
2259 break;
2260 XUngrabPointer(dpy, CurrentTime);
2261 return;
2263 default:
2264 WMHandleEvent(&ev);
2265 break;
2271 *----------------------------------------------------------------------
2272 * menuCloseClick--
2273 * Handles mouse click on the close button of menus. The menu is
2274 * closed when the button is clicked.
2276 * Side effects:
2277 * The closed menu is reinserted at it's parent menus
2278 * cascade list.
2279 *----------------------------------------------------------------------
2281 static void menuCloseClick(WCoreWindow * sender, void *data, XEvent * event)
2283 WMenu *menu = (WMenu *) data;
2284 WMenu *parent = menu->parent;
2285 int i;
2287 if (parent) {
2288 for (i = 0; i < parent->cascade_no; i++) {
2289 /* find the entry that points to the copy */
2290 if (parent->cascades[i] == menu->brother) {
2291 /* make it point to the original */
2292 parent->cascades[i] = menu;
2293 menu->parent = parent;
2294 break;
2298 wMenuUnmap(menu);
2301 static void saveMenuInfo(WMPropList * dict, WMenu * menu, WMPropList * key)
2303 WMPropList *value, *list;
2304 char buffer[256];
2306 snprintf(buffer, sizeof(buffer), "%i,%i", menu->frame_x, menu->frame_y);
2307 value = WMCreatePLString(buffer);
2308 list = WMCreatePLArray(value, NULL);
2309 if (menu->flags.lowered)
2310 WMAddToPLArray(list, WMCreatePLString("lowered"));
2311 WMPutInPLDictionary(dict, key, list);
2312 WMReleasePropList(value);
2313 WMReleasePropList(list);
2316 void wMenuSaveState(WScreen * scr)
2318 WMPropList *menus, *key;
2319 int save_menus = 0;
2321 menus = WMCreatePLDictionary(NULL, NULL);
2323 if (scr->switch_menu && scr->switch_menu->flags.buttoned) {
2324 key = WMCreatePLString("SwitchMenu");
2325 saveMenuInfo(menus, scr->switch_menu, key);
2326 WMReleasePropList(key);
2327 save_menus = 1;
2330 if (saveMenuRecurs(menus, scr, scr->root_menu))
2331 save_menus = 1;
2333 if (scr->workspace_menu && scr->workspace_menu->flags.buttoned) {
2334 key = WMCreatePLString("WorkspaceMenu");
2335 saveMenuInfo(menus, scr->workspace_menu, key);
2336 WMReleasePropList(key);
2337 save_menus = 1;
2340 if (save_menus) {
2341 key = WMCreatePLString("Menus");
2342 WMPutInPLDictionary(scr->session_state, key, menus);
2343 WMReleasePropList(key);
2345 WMReleasePropList(menus);
2348 static Bool getMenuPath(WMenu * menu, char *buffer, int bufSize)
2350 Bool ok = True;
2351 int len = 0;
2353 if (!menu->flags.titled || !menu->frame->title[0])
2354 return False;
2356 len = strlen(menu->frame->title);
2357 if (len >= bufSize)
2358 return False;
2360 if (menu->parent) {
2361 ok = getMenuPath(menu->parent, buffer, bufSize - len - 1);
2362 if (!ok)
2363 return False;
2366 strcat(buffer, "\\");
2367 strcat(buffer, menu->frame->title);
2369 return True;
2372 static Bool saveMenuRecurs(WMPropList * menus, WScreen * scr, WMenu * menu)
2374 WMPropList *key;
2375 int save_menus = 0, i;
2376 char buffer[512];
2377 Bool ok = True;
2379 if (menu->flags.brother)
2380 menu = menu->brother;
2382 if (menu->flags.buttoned && menu != scr->switch_menu) {
2384 buffer[0] = '\0';
2385 ok = getMenuPath(menu, buffer, 510);
2387 if (ok) {
2388 key = WMCreatePLString(buffer);
2389 saveMenuInfo(menus, menu, key);
2390 WMReleasePropList(key);
2391 save_menus = 1;
2395 if (ok) {
2396 for (i = 0; i < menu->cascade_no; i++) {
2397 if (saveMenuRecurs(menus, scr, menu->cascades[i]))
2398 save_menus = 1;
2401 return save_menus;
2404 #define COMPLAIN(key) wwarning(_("bad value in menus state info: %s"), key)
2406 static Bool getMenuInfo(WMPropList * info, int *x, int *y, Bool * lowered)
2408 WMPropList *pos;
2410 *lowered = False;
2412 if (WMIsPLArray(info)) {
2413 WMPropList *flags;
2414 pos = WMGetFromPLArray(info, 0);
2415 flags = WMGetFromPLArray(info, 1);
2416 if (flags != NULL && WMIsPLString(flags) && WMGetFromPLString(flags) != NULL
2417 && strcmp(WMGetFromPLString(flags), "lowered") == 0) {
2418 *lowered = True;
2420 } else {
2421 pos = info;
2424 if (pos != NULL && WMIsPLString(pos)) {
2425 if (sscanf(WMGetFromPLString(pos), "%i,%i", x, y) != 2)
2426 COMPLAIN("Position");
2427 } else {
2428 COMPLAIN("(position, flags...)");
2429 return False;
2432 return True;
2435 static int restoreMenu(WScreen * scr, WMPropList * menu, int which)
2437 int x, y;
2438 Bool lowered = False;
2439 WMenu *pmenu = NULL;
2441 if (!menu)
2442 return False;
2444 if (!getMenuInfo(menu, &x, &y, &lowered))
2445 return False;
2447 if (which & WSS_SWITCHMENU) {
2448 OpenSwitchMenu(scr, x, y, False);
2449 pmenu = scr->switch_menu;
2452 if (pmenu) {
2453 int width = MENUW(pmenu);
2454 int height = MENUH(pmenu);
2455 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
2457 if (lowered) {
2458 changeMenuLevels(pmenu, True);
2461 if (x < rect.pos.x - width)
2462 x = rect.pos.x;
2463 if (x > rect.pos.x + rect.size.width)
2464 x = rect.pos.x + rect.size.width - width;
2465 if (y < rect.pos.y)
2466 y = rect.pos.y;
2467 if (y > rect.pos.y + rect.size.height)
2468 y = rect.pos.y + rect.size.height - height;
2470 wMenuMove(pmenu, x, y, True);
2471 pmenu->flags.buttoned = 1;
2472 wFrameWindowShowButton(pmenu->frame, WFF_RIGHT_BUTTON);
2473 return True;
2475 return False;
2478 static int restoreMenuRecurs(WScreen * scr, WMPropList * menus, WMenu * menu, char *path)
2480 WMPropList *key, *entry;
2481 char buffer[512];
2482 int i, x, y, res;
2483 Bool lowered;
2485 if (strlen(path) + strlen(menu->frame->title) > 510)
2486 return False;
2488 snprintf(buffer, sizeof(buffer), "%s\\%s", path, menu->frame->title);
2489 key = WMCreatePLString(buffer);
2490 entry = WMGetFromPLDictionary(menus, key);
2491 res = False;
2493 if (entry && getMenuInfo(entry, &x, &y, &lowered)) {
2495 if (!menu->flags.mapped) {
2496 int width = MENUW(menu);
2497 int height = MENUH(menu);
2498 WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr));
2500 wMenuMapAt(menu, x, y, False);
2502 if (menu->parent) {
2503 /* make parent map the copy in place of the original */
2504 for (i = 0; i < menu->parent->cascade_no; i++) {
2505 if (menu->parent->cascades[i] == menu) {
2506 menu->parent->cascades[i] = menu->brother;
2507 break;
2511 if (lowered) {
2512 changeMenuLevels(menu, True);
2515 if (x < rect.pos.x - width)
2516 x = rect.pos.x;
2517 if (x > rect.pos.x + rect.size.width)
2518 x = rect.pos.x + rect.size.width - width;
2519 if (y < rect.pos.y)
2520 y = rect.pos.y;
2521 if (y > rect.pos.y + rect.size.height)
2522 y = rect.pos.y + rect.size.height - height;
2524 wMenuMove(menu, x, y, True);
2525 menu->flags.buttoned = 1;
2526 wFrameWindowShowButton(menu->frame, WFF_RIGHT_BUTTON);
2527 res = True;
2531 WMReleasePropList(key);
2533 for (i = 0; i < menu->cascade_no; i++) {
2534 if (restoreMenuRecurs(scr, menus, menu->cascades[i], buffer) != False)
2535 res = True;
2538 return res;
2541 void wMenuRestoreState(WScreen * scr)
2543 WMPropList *menus, *menu, *key, *skey;
2545 if (!scr->session_state) {
2546 return;
2549 key = WMCreatePLString("Menus");
2550 menus = WMGetFromPLDictionary(scr->session_state, key);
2551 WMReleasePropList(key);
2553 if (!menus)
2554 return;
2556 /* restore menus */
2558 skey = WMCreatePLString("SwitchMenu");
2559 menu = WMGetFromPLDictionary(menus, skey);
2560 WMReleasePropList(skey);
2561 restoreMenu(scr, menu, WSS_SWITCHMENU);
2563 if (!scr->root_menu) {
2564 OpenRootMenu(scr, scr->scr_width * 2, 0, False);
2565 wMenuUnmap(scr->root_menu);
2567 restoreMenuRecurs(scr, menus, scr->root_menu, "");
2570 void OpenWorkspaceMenu(WScreen * scr, int x, int y)
2572 WMenu *menu, *parent;
2573 WMenuEntry *entry;
2575 if (!scr->root_menu) {
2576 OpenRootMenu(scr, scr->scr_width * 2, 0, False);
2577 wMenuUnmap(scr->root_menu);
2580 menu = scr->workspace_menu;
2581 if (menu) {
2582 if (menu->flags.mapped) {
2583 if (!menu->flags.buttoned) {
2584 wMenuUnmap(menu);
2585 parent = menu->parent;
2586 if (parent && parent->selected_entry >= 0) {
2587 entry = parent->entries[parent->selected_entry];
2588 if (parent->cascades[entry->cascade] == menu) {
2589 selectEntry(parent, -1);
2590 wMenuMapAt(menu, x, y, False);
2593 } else {
2594 wRaiseFrame(menu->frame->core);
2595 wMenuMapCopyAt(menu, x, y);
2597 } else {
2598 wMenuMapAt(menu, x, y, False);