/* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /***************************************************************************** * This module is all original code * by Rob Nation * Copyright 1993, Robert Nation * You may use this code for any purpose, as long as the original * copyright remains in the source code and all documentation ****************************************************************************/ /*********************************************************************** * * fvwm menu code * ***********************************************************************/ /* #define FVWM_DEBUG_MSGS */ #include "config.h" #include #include #include #include #include #include "fvwm.h" #include "externs.h" #include "events.h" #include "menus.h" #include "cursor.h" #include "functions.h" #include "repeat.h" #include "borders.h" #include "misc.h" #include "screen.h" #include "colors.h" #include "colormaps.h" #include "decorations.h" #include "libs/Colorset.h" #include "defaults.h" #include "libs/FScreen.h" /* IMPORTANT NOTE: Do *not* use any constant numbers in this file. All values * have to be #defined in the section below or defaults.h to ensure full * control over the menus. */ /* used in float to int arithmetic */ #define ROUNDING_ERROR_TOLERANCE 0.005 /*************************************************************** * typedefs ***************************************************************/ typedef struct { unsigned int keystate; unsigned int keycode; Time timestamp; } double_keypress; /* patch to pass the last popups position hints to popup_func */ typedef struct { struct { unsigned is_last_menu_pos_hints_valid : 1; unsigned do_ignore_pos_hints : 1; } flags; MenuPosHints pos_hints; } saved_pos_hints; /*************************************************************** * statics ***************************************************************/ /* This global is saved and restored every time a function is called that * might modify them, so we can safely let it live outside a function. */ static saved_pos_hints last_saved_pos_hints = { { False, False }, { 0, 0, 0.0, 0.0, 0, 0, False, False, False } }; /*************************************************************** * externals ***************************************************************/ /* This external is safe. It's written only during startup. */ extern XContext MenuContext; /* We need to use this structure to make sure the other parts of fvwm work with * the most recent event. */ extern XEvent Event; /* This one is only read, never written */ extern Time lastTimestamp; /*************************************************************** * forward declarations ***************************************************************/ static void draw_separator(Window, GC,GC,int, int,int,int,int); static void draw_underline(MenuRoot *mr, GC gc, int x, int y, char *txt, int coffset); static void MenuInteraction( MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp, Bool *pdo_warp_to_item); static int pop_menu_up( MenuRoot **pmenu, MenuParameters *pmp, MenuRoot *parent_menu, MenuItem *parent_item, FvwmWindow **pfw, int *pcontext, int x, int y, Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops, Bool *ret_overlap, Bool *pdo_warp_to_title, Window popdown_window); static void pop_menu_down(MenuRoot **pmr, MenuParameters *pmp); static void pop_menu_down_and_repaint_parent( MenuRoot **pmr, Bool *fSubmenuOverlaps, MenuParameters *pmp); static void get_popup_options( MenuParameters *pmp, MenuItem *mi, MenuOptions *pops); static void paint_item(MenuRoot *mr, MenuItem *mi, FvwmWindow *fw, Bool do_redraw_menu_border); static void paint_menu(MenuRoot *, XEvent *, FvwmWindow *fw); static void select_menu_item( MenuRoot *mr, MenuItem *mi, Bool select, FvwmWindow *fw); static MenuRoot *copy_menu_root(MenuRoot *mr); static void make_menu(MenuRoot *mr, MenuParameters *pmp); static void make_menu_window(MenuRoot *mr); static void get_xy_from_position_hints( MenuPosHints *ph, int width, int height, int context_width, Bool do_reverse_x, int *ret_x, int *ret_y); static MenuRoot *FindPopup(char *popup_name); static void merge_continuation_menus(MenuRoot *mr); /*************************************************************** * * subroutines * ***************************************************************/ /*************************************************************** * functions dealing with coordinates ***************************************************************/ static int item_middle_y_offset(MenuRoot *mr, MenuItem *mi) { int r; if (!mi) return MST_BORDER_WIDTH(mr); r = (MI_IS_SELECTABLE(mi)) ? MST_RELIEF_THICKNESS(mr) : 0; return MI_Y_OFFSET(mi) + (MI_HEIGHT(mi) + r) / 2; } static int menu_middle_x_offset(MenuRoot *mr) { return MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) / 2; } static Bool pointer_in_active_item_area(int x_offset, MenuRoot *mr) { if (MST_USE_LEFT_SUBMENUS(mr)) { return (x_offset <= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - MR_ITEM_WIDTH(mr) * MENU_POPUP_NOW_RATIO); } else { return (x_offset >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * MENU_POPUP_NOW_RATIO); } } static Bool pointer_in_passive_item_area(int x_offset, MenuRoot *mr) { if (MST_USE_LEFT_SUBMENUS(mr)) { return (x_offset >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * MENU_POPUP_NOW_RATIO); } else { return (x_offset <= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - MR_ITEM_WIDTH(mr) * MENU_POPUP_NOW_RATIO); } } static int get_left_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x) { if (MST_USE_LEFT_SUBMENUS(mr)) { return (x - MST_POPUP_OFFSET_ADD(mr) - MR_WIDTH(submenu) + MR_WIDTH(mr) * (100 - MST_POPUP_OFFSET_PERCENT(mr))/100); } else { return (x - MR_WIDTH(submenu) + MST_BORDER_WIDTH(mr)); } } static int get_right_popup_x_position(MenuRoot *mr, MenuRoot *submenu, int x) { if (MST_USE_LEFT_SUBMENUS(mr)) { return (x + MR_WIDTH(mr) - MST_BORDER_WIDTH(mr)); } else { return (x + MR_WIDTH(mr) * MST_POPUP_OFFSET_PERCENT(mr) / 100 + MST_POPUP_OFFSET_ADD(mr)); } } static void get_prefered_popup_position( MenuRoot *mr, MenuRoot *submenu, int *px, int *py, Bool *pprefer_left_submenus) { int menu_x, menu_y; MenuItem *mi = NULL; if (!XGetGeometry(dpy, MR_WINDOW(mr), &JunkRoot, &menu_x, &menu_y, &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth)) { *px = 0; *py = 0; *pprefer_left_submenus = False; fvwm_msg(ERR, "get_prefered_popup_position", "can't get geometry of menu %s", MR_NAME(mr)); return; } /* set the direction flag */ *pprefer_left_submenus = (MR_HAS_POPPED_UP_LEFT(mr) || (MST_USE_LEFT_SUBMENUS(mr) && !MR_HAS_POPPED_UP_RIGHT(mr))); /* get the x position */ if (*pprefer_left_submenus) { *px = get_left_popup_x_position(mr, submenu, menu_x); } else { *px = get_right_popup_x_position(mr, submenu, menu_x); } /* get the y position */ if (MR_SELECTED_ITEM(mr)) { mi = MR_SELECTED_ITEM(mr); } if (mi) { *py = menu_y + MI_Y_OFFSET(mi) - MST_BORDER_WIDTH(mr) + MST_RELIEF_THICKNESS(mr); } else { *py = menu_y; } } /*************************************************************** * warping functions ***************************************************************/ static void warp_pointer_to_title(MenuRoot *mr) { XWarpPointer(dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0, menu_middle_x_offset(mr), item_middle_y_offset(mr, MR_FIRST_ITEM(mr))); } static MenuItem *warp_pointer_to_item(MenuRoot *mr, MenuItem *mi, Bool do_skip_title) { if (do_skip_title) { while (MI_NEXT_ITEM(mi) != NULL && (MI_IS_SEPARATOR(mi) || MI_IS_TITLE(mi))) { /* skip separators and titles until the first 'real' item is found */ mi = MI_NEXT_ITEM(mi); } } if (mi == NULL) mi = MR_LAST_ITEM(mr); if (mi == NULL) return mi; XWarpPointer(dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0, menu_middle_x_offset(mr), item_middle_y_offset(mr, mi)); return mi; } /*************************************************************** * menu animation functions ***************************************************************/ /* Undo the animation of a menu */ static void animated_move_back(MenuRoot *mr, Bool do_warp_pointer) { int act_x; int act_y; if (MR_XANIMATION(mr) == 0) return; if (XGetGeometry(dpy, MR_WINDOW(mr), &JunkRoot, &act_x, &act_y, &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth)) { /* move it back */ AnimatedMoveOfWindow( MR_WINDOW(mr) ,act_x, act_y, act_x - MR_XANIMATION(mr), act_y, do_warp_pointer, -1, NULL); MR_XANIMATION(mr) = 0; } return; } /*************************************************************** * functions dealing with menu items and indices ***************************************************************/ /*********************************************************************** * find_entry() * * Returns the menu item the pointer is over and optionally the offset * from the left side of the menu entry (if px_offset is != NULL) and * the MenuRoot the pointer is over (if pmr is != NULL). ***********************************************************************/ static MenuItem *find_entry( int *px_offset /*NULL means don't return this value */, MenuRoot **pmr /*NULL means don't return this value */, /* values passed in from caller it XQueryPointer was already called there */ Window p_child, int p_rx, int p_ry) { MenuItem *mi; MenuRoot *mr; int root_x, root_y; int x, y; Window Child; int r; /* x_offset returns the x offset of the pointer in the found menu item */ if (px_offset) *px_offset = 0; if (pmr) *pmr = NULL; if (p_rx < 0) { if (!XQueryPointer(dpy, Scr.Root, &JunkRoot, &Child, &root_x, &root_y, &JunkX, &JunkY, &JunkMask)) { return NULL; } } else { root_x = p_rx; root_y = p_ry; Child = p_child; } if (XFindContext(dpy, Child, MenuContext, (caddr_t *)&mr) == XCNOENT) { return NULL; } if (pmr) *pmr = mr; /* now get position in that child window */ if (!XTranslateCoordinates( dpy, Scr.Root, Child, root_x, root_y, &x, &y, &JunkChild)) { return NULL; } r = MST_RELIEF_THICKNESS(mr); /* look for the entry that the mouse is in */ for (mi = MR_FIRST_ITEM(mr); mi; mi = MI_NEXT_ITEM(mi)) { int a; int b; a = (MI_PREV_ITEM(mi) && MI_IS_SELECTABLE(MI_PREV_ITEM(mi))) ? r / 2 : 0; if (!MI_IS_SELECTABLE(mi)) b = 0; else if (MI_NEXT_ITEM(mi) && MI_IS_SELECTABLE(MI_NEXT_ITEM(mi))) b = r / 2; else b = r; if (y >= MI_Y_OFFSET(mi) - a && y < MI_Y_OFFSET(mi) + MI_HEIGHT(mi) + b) { break; } } if (x < MR_ITEM_X_OFFSET(mr) || x >= MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1) { mi = NULL; } if (mi && px_offset) *px_offset = x; return mi; } /*************************************************************** * Returns the position of the item in the menu, but counts * only items that can be selected (i.e. nor separators or * titles). The count begins with 0. ***************************************************************/ static int get_selectable_item_index( MenuRoot *mr, MenuItem *miTarget, int *ret_sections) { int i = 0; int s = 0; MenuItem *mi; Bool last_selectable = False; for (mi = MR_FIRST_ITEM(mr); mi && mi != miTarget; mi = MI_NEXT_ITEM(mi)) { if (!MI_IS_SEPARATOR(mi) && !MI_IS_TITLE(mi)) { i++; last_selectable = True; } else if (last_selectable) { s++; last_selectable = False; } } if (ret_sections) { *ret_sections = s; } if (mi == miTarget) { return i; } return -1; } static MenuItem *get_selectable_item_from_index(MenuRoot *mr, int index) { int i = -1; MenuItem *mi; MenuItem *miLastOk = NULL; for (mi = MR_FIRST_ITEM(mr); mi && (i < index || miLastOk == NULL); mi=MI_NEXT_ITEM(mi)) { if (!MI_IS_SEPARATOR(mi) && !MI_IS_TITLE(mi)) { miLastOk = mi; i++; } } return miLastOk; } static MenuItem *get_selectable_item_from_section(MenuRoot *mr, int section) { int i = 0; MenuItem *mi; MenuItem *miLastOk = NULL; Bool last_selectable = False; for (mi = MR_FIRST_ITEM(mr); mi && (i <= section || miLastOk == NULL); mi=MI_NEXT_ITEM(mi)) { if (!MI_IS_SEPARATOR(mi) && !MI_IS_TITLE(mi)) { if (!last_selectable) { miLastOk = mi; last_selectable = True; } } else if (last_selectable) { i++; last_selectable = False; } } return miLastOk; } static int get_selectable_item_count(MenuRoot *mr, int *ret_sections) { int count; count = get_selectable_item_index(mr, MR_LAST_ITEM(mr), ret_sections); if (MR_LAST_ITEM(mr) && MI_IS_SELECTABLE(MR_LAST_ITEM(mr))) count++; return count; } /*************************************************************** * functions dealing with submenus ***************************************************************/ /* Search for a submenu that was popped up by the given item in the given * instance of the menu. */ static MenuRoot *seek_submenu_instance(MenuRoot *parent_menu, MenuItem *parent_item) { MenuRoot *mr; for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr)) { if (MR_PARENT_MENU(mr) == parent_menu && MR_PARENT_ITEM(mr) == parent_item) { /* here it is */ break; } } return mr; } static Bool is_submenu_mapped(MenuRoot *parent_menu, MenuItem *parent_item) { XWindowAttributes win_attribs; MenuRoot *mr; mr = seek_submenu_instance(parent_menu, parent_item); if (mr == NULL) return False; if (MR_WINDOW(mr) == None) return False; if (!XGetWindowAttributes(dpy, MR_WINDOW(mr), &win_attribs)) return False; return (win_attribs.map_state == IsViewable); } /* Returns the menu root that a given menu item pops up */ static MenuRoot *mr_popup_for_mi(MenuRoot *mr, MenuItem *mi) { char *menu_name; MenuRoot *menu = NULL; /* This checks if mi is != NULL too */ if (!mi || !MI_IS_POPUP(mi)) return NULL; /* first look for a menu that is aleady mapped */ menu = seek_submenu_instance(mr, mi); if (menu) return menu; /* just look past "Popup " in the action, and find that menu root */ menu_name = PeekToken(SkipNTokens(MI_ACTION(mi), 1), NULL); menu = FindPopup(menu_name); return menu; } /* FindPopup expects a token as the input. Make sure you have used * GetNextToken before passing a menu name to remove quotes (if necessary) */ static MenuRoot *FindPopup(char *popup_name) { MenuRoot *mr; if (popup_name == NULL) return NULL; mr = Menus.all; while (mr != NULL) { if (MR_NAME(mr) != NULL) { if (mr == MR_ORIGINAL_MENU(mr) && StrEquals(popup_name, MR_NAME(mr))) { return mr; } } mr = MR_NEXT_MENU(mr); } return NULL; } /*************************************************************** * functions ***************************************************************/ /* Make sure the menu is properly rebuilt when the style or the menu has * changed. */ static void update_menu(MenuRoot *mr, MenuParameters *pmp) { int sw; int sh; Bool has_screen_size_changed = False; fscreen_scr_arg fscr; if (MST_IS_UPDATED(mr)) { /* The menu style has changed. */ MenuRoot *menu; for (menu = Menus.all; menu; menu = MR_NEXT_MENU(menu)) { if (MR_STYLE(menu) == MR_STYLE(mr)) { /* Mark all other menus with the same style as changed. */ MR_IS_UPDATED(menu) = 1; } } MST_IS_UPDATED(mr) = 0; } fscr.xypos.x = pmp->screen_origin_x; fscr.xypos.y = pmp->screen_origin_y; FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &JunkX, &JunkY, &sw, &sh); if (sw != MR_SCREEN_WIDTH(mr) || sh != MR_SCREEN_HEIGHT(mr)) { has_screen_size_changed = True; MR_SCREEN_WIDTH(mr) = (unsigned short)sw; MR_SCREEN_HEIGHT(mr) = (unsigned short)sh; } if (MR_IS_UPDATED(mr) || has_screen_size_changed) { /* The menu or the screen dimensions have changed. We have to re-make it. */ make_menu(mr, pmp); } return; } /**************************************************************************** * * Initiates a menu pop-up * * fStick = True = sticky menu, stays up on initial button release. * fStick = False = transient menu, drops on initial release. * * eventp = 0: menu opened by mouse, do not warp * eventp > 1: root menu opened by keypress with 'Menu', warp pointer and * allow 'double-keypress'. * eventp = 1: menu opened by keypress, warp but forbid 'double-keypress' * this should always be used except in the call in 'staysup_func' * in builtin.c * * Returns one of MENU_NOP, MENU_ERROR, MENU_ABORTED, MENU_DONE ***************************************************************************/ void do_menu(MenuParameters *pmp, MenuReturn *pmret) { extern XEvent Event; int x; int y; Bool fFailedPopup = False; Bool fWasAlreadyPopped = False; Bool key_press; Time t0 = lastTimestamp; XEvent tmpevent; double_keypress dkp; Bool do_warp; /* must be saved before launching parallel menus (by using the dynamic * actions). */ static Bool do_warp_to_title = False; /* don't save these ones, we want them to work even within recursive menus * popped up by dynamic actions. */ static int indirect_depth = 0; static int x_start; static int y_start; static Bool has_mouse_moved = False; int scr_x, scr_y, scr_w, scr_h; pmret->rc = MENU_NOP; if (pmp->flags.is_sticky && !pmp->flags.is_submenu) { XCheckTypedEvent(dpy, ButtonPressMask, &tmpevent); } if (pmp->menu == NULL) { pmret->rc = MENU_ERROR; return; } key_press = (pmp->eventp && (pmp->eventp == (XEvent *)1 || pmp->eventp->type == KeyPress)); /* Try to pick a root-relative optimal x,y to * put the mouse right on the title w/o warping */ XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &x, &y, &JunkX, &JunkY, &JunkMask); /* Save these-- we want to warp back here if this is a top level menu brought up by a keystroke */ if (!pmp->flags.is_submenu) { pmp->flags.is_invoked_by_key_press = key_press; pmp->flags.is_first_root_menu = !indirect_depth; } else { pmp->flags.is_first_root_menu = 0; } if (!pmp->flags.is_submenu && indirect_depth == 0) { if (key_press) { x_start = x; y_start = y; } else { x_start = -1; y_start = -1; } pmp->screen_origin_x = pmp->pops->pos_hints.screen_origin_x; pmp->screen_origin_y = pmp->pops->pos_hints.screen_origin_y; } if (pmp->pops->flags.do_warp_title) { do_warp = True; } else if (pmp->pops->flags.do_not_warp) { do_warp = False; } else if (key_press) { do_warp = True; } else { do_warp = False; } /* Figure out where we should popup, if possible */ if (!pmp->flags.is_already_mapped) { Bool prefer_left_submenus = False; /* Make sure we are using the latest style and menu layout. */ update_menu(pmp->menu, pmp); if (pmp->flags.is_submenu) { /* this is a submenu popup */ get_prefered_popup_position( pmp->parent_menu, pmp->menu, &x, &y, &prefer_left_submenus); } else { fscreen_scr_arg fscr; /* we're a top level menu */ has_mouse_moved = False; if(!GrabEm(CRS_MENU, GRAB_MENU)) { /* GrabEm specifies the cursor to use */ XBell(dpy, 0); pmret->rc = MENU_ABORTED; return; } /* Make the menu appear under the pointer rather than warping */ fscr.xypos.x = x; fscr.xypos.y = y; FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w, &scr_h); x -= menu_middle_x_offset(pmp->menu); y -= item_middle_y_offset(pmp->menu, MR_FIRST_ITEM(pmp->menu)); if (x < scr_x) { x = scr_x; } if (y < scr_y) { y = scr_y; } } /* pop_menu_up may move the x,y to make it fit on screen more nicely */ /* it might also move parent_menu out of the way */ if (!pop_menu_up( &(pmp->menu), pmp, pmp->parent_menu, NULL, pmp->pTmp_win, pmp->pcontext, x, y, prefer_left_submenus, do_warp, pmp->pops, NULL, &do_warp_to_title, None)) { fFailedPopup = True; XBell(dpy, 0); UngrabEm(GRAB_MENU); pmret->rc = MENU_ERROR; return; } } else { fWasAlreadyPopped = True; if (key_press) { warp_pointer_to_item(pmp->menu, MR_FIRST_ITEM(pmp->menu), True /* skip Title */); } } do_warp_to_title = False; /* Remember the key that popped up the root menu. */ if (pmp->flags.is_submenu) { dkp.timestamp = 0; } else { if (pmp->eventp && pmp->eventp != (XEvent *)1) { /* we have a real key event */ dkp.keystate = pmp->eventp->xkey.state; dkp.keycode = pmp->eventp->xkey.keycode; } dkp.timestamp = (key_press && pmp->flags.has_default_action) ? t0 : 0; } if(!pmp->flags.is_submenu && indirect_depth == 0) { /* we need to grab the keyboard so we are sure no key presses are lost */ MyXGrabKeyboard(dpy); } if (!fFailedPopup) { if (!pmp->flags.is_submenu) { XSelectInput(dpy, Scr.NoFocusWin, XEVMASK_MENUNFW); } MenuInteraction(pmp, pmret, &dkp, &do_warp_to_title); if (!pmp->flags.is_submenu) { XSelectInput(dpy, Scr.NoFocusWin, XEVMASK_NOFOCUSW); } } else { pmret->rc = MENU_ABORTED; } if (!fWasAlreadyPopped) { /* popping down the menu may destroy the menu via the dynamic popdown * action! */ pop_menu_down(&pmp->menu, pmp); } pmp->flags.is_menu_from_frame_or_window_or_titlebar = False; XFlush(dpy); if (!pmp->flags.is_submenu && x_start >= 0 && y_start >= 0 && pmret->flags.is_key_press) { /* warp pointer back to where invoked if this was brought up * with a keypress and we're returning from a top level menu, * and a button release event didn't end it */ XWarpPointer(dpy, 0, Scr.Root, 0, 0, Scr.MyDisplayWidth, Scr.MyDisplayHeight, x_start, y_start); if (Event.type == KeyPress) { Event.xkey.x_root = x_start; Event.xkey.y_root = y_start; } } dkp.timestamp = 0; if(!pmp->flags.is_submenu) { UngrabEm(GRAB_MENU); WaitForButtonsUp(True); if (pmret->rc == MENU_DONE) { if (pmp->ret_paction && *(pmp->ret_paction)) { exec_func_args_type efa; indirect_depth++; memset(&efa, 0, sizeof(efa)); efa.eventp = &Event; efa.tmp_win = pmp->button_window; efa.action = *(pmp->ret_paction); efa.context = *(pmp->pcontext); efa.module = -1; efa.flags.do_save_tmpwin = 1; execute_function(&efa); indirect_depth--; free(*(pmp->ret_paction)); *(pmp->ret_paction) = NULL; } last_saved_pos_hints.flags.do_ignore_pos_hints = False; last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False; } if (indirect_depth == 0) { last_saved_pos_hints.flags.do_ignore_pos_hints = False; last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False; } } if(!pmp->flags.is_submenu && indirect_depth == 0) { /* release the keyboard when the last menu closes */ MyXUngrabKeyboard(dpy); } return; } /*********************************************************************** * Procedure * menuShortcuts() - Menu keyboard processing * * Function called from MenuInteraction instead of Keyboard_Shortcuts() * when a KeyPress event is received. If the key is alphanumeric, * then the menu is scanned for a matching hot key. Otherwise if * it was the escape key then the menu processing is aborted. * If none of these conditions are true, then the default processing * routine is called. * TKP - uses XLookupString so that keypad numbers work with windowlist ***********************************************************************/ typedef enum { SA_NONE = 0, SA_ENTER, SA_LEAVE, SA_MOVE_ITEMS, SA_FIRST, SA_LAST, SA_CONTINUE, SA_WARPBACK, SA_SELECT, #ifdef TEAR_OFF_MENUS SA_TEAROFF, #endif SA_ABORT } shortcut_action; static void menuShortcuts( MenuRoot *mr, MenuParameters *pmp, MenuReturn *pmret, XEvent *event, MenuItem **pmiCurrent, double_keypress *pdkp) { int fControlKey = event->xkey.state & ControlMask? True : False; int fShiftedKey = event->xkey.state & ShiftMask? True: False; int fMetaKey = event->xkey.state & Mod1Mask? True: False; KeySym keysym; char ckeychar = 0; int ikeychar; MenuItem *newItem = NULL; MenuItem *miCurrent = pmiCurrent ? *pmiCurrent : NULL; int index; int mx; int my; int menu_x; int menu_y; unsigned int menu_width; unsigned int menu_height; int items_to_move; Bool fSkipSection = False; shortcut_action saction = SA_NONE; if (event->type == KeyRelease) { /* This function is only called with a KeyRelease event if the user released * the 'select' key (s)he configured. */ pmret->rc = MENU_SELECTED; return; } /*** handle double-keypress ***/ if (pdkp->timestamp && lastTimestamp - pdkp->timestamp < MST_DOUBLE_CLICK_TIME(pmp->menu) && event->xkey.state == pdkp->keystate && event->xkey.keycode == pdkp->keycode) { *pmiCurrent = NULL; pmret->rc = MENU_DOUBLE_CLICKED; return; } pdkp->timestamp = 0; /*** find out the key ***/ /* Is it okay to treat keysym-s as Ascii? * No, because the keypad numbers don't work. Use XlookupString */ index = XLookupString(&(event->xkey), &ckeychar, 1, &keysym, NULL); ikeychar = (int)ckeychar; /*** Try to match hot keys ***/ /* Need isascii here - isgraph might coredump! */ if (index == 1 && isascii(ikeychar) && isgraph(ikeychar) && fControlKey == False && fMetaKey == False) { /* allow any printable character to be a keysym, but be sure control isn't pressed */ MenuItem *mi; MenuItem *mi1; int key; int countHotkey = 0; /* Added by MMH mikehan@best.com 2/7/99 */ /* if this is a letter set it to lower case */ if (isupper(ikeychar)) ikeychar = tolower(ikeychar) ; /* MMH mikehan@best.com 2/7/99 * Multiple hotkeys per menu * Search menu for matching hotkey; * remember how many we found and where we found it */ mi = ( miCurrent == NULL || miCurrent == MR_LAST_ITEM(mr)) ? MR_FIRST_ITEM(mr) : MI_NEXT_ITEM(miCurrent); mi1 = mi; do { if (MI_HAS_HOTKEY(mi) && !MI_IS_TITLE(mi) && (!MI_IS_HOTKEY_AUTOMATIC(mi) || MST_USE_AUTOMATIC_HOTKEYS(mr))) { key = (MI_LABEL(mi)[(int)MI_HOTKEY_COLUMN(mi)])[MI_HOTKEY_COFFSET(mi)]; key = tolower(key); if ( ikeychar == key ) { if ( ++countHotkey == 1 ) newItem = mi; } } mi = (mi == MR_LAST_ITEM(mr)) ? MR_FIRST_ITEM(mr) : MI_NEXT_ITEM(mi); } while (mi != mi1); /* For multiple instances of a single hotkey, just move the selection */ if (countHotkey > 1) { *pmiCurrent = newItem; pmret->rc = MENU_NEWITEM; return; } /* Do things the old way for unique hotkeys in the menu */ else if (countHotkey == 1) { *pmiCurrent = newItem; if (newItem && MI_IS_POPUP(newItem)) pmret->rc = MENU_POPUP; else pmret->rc = MENU_SELECTED; return; } /* MMH mikehan@best.com 2/7/99 */ } /*** now determine the action to take ***/ items_to_move = 0; pmret->rc = MENU_NOP; switch(keysym) { case XK_Escape: case XK_Delete: case XK_KP_Separator: /* abort */ saction = SA_ABORT; break; case XK_space: case XK_Return: case XK_KP_Enter: /* select menu item */ saction = SA_SELECT; break; case XK_Insert: case XK_KP_0: /* move to last entry of menu ('More...' if this exists) and try to enter * the menu. Otherwise try to enter the current submenu */ saction = (MR_CONTINUATION_MENU(mr) != NULL) ? SA_CONTINUE : SA_ENTER; break; case XK_Left: case XK_KP_4: case XK_h: /* vi left */ /* leave or enter a submenu */ saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_ENTER : SA_LEAVE; break; case XK_Right: case XK_KP_6: case XK_l: /* vi right */ /* enter or leave a submenu */ saction = (MST_USE_LEFT_SUBMENUS(mr)) ? SA_LEAVE : SA_ENTER; break; case XK_b: /* back */ /* leave menu */ saction = SA_LEAVE; break; case XK_f: /* forward */ /* enter menu */ saction = SA_ENTER; break; case XK_Page_Up: case XK_KP_9: items_to_move = -5; saction = SA_MOVE_ITEMS; break; case XK_Page_Down: case XK_KP_3: items_to_move = 5; saction = SA_MOVE_ITEMS; break; case XK_Up: case XK_KP_8: case XK_k: /* vi up */ case XK_p: /* prior */ if (fShiftedKey && !fControlKey && !fMetaKey) { saction = SA_FIRST; break; } else if (fControlKey) items_to_move = -5; else if (fMetaKey) { items_to_move = -1; fSkipSection = True; } else items_to_move = -1; saction = SA_MOVE_ITEMS; break; case XK_Down: case XK_KP_2: case XK_j: /* vi down */ case XK_n: /* next */ if (fShiftedKey && !fControlKey && !fMetaKey) { saction = SA_LAST; break; } else if (fControlKey) items_to_move = 5; else if (fMetaKey) { items_to_move = 1; fSkipSection = True; } else items_to_move = 1; saction = SA_MOVE_ITEMS; break; case XK_Tab: #ifdef XK_XKB_KEYS case XK_ISO_Left_Tab: #endif /* Tab added mostly for Winlist */ items_to_move = 1; switch (fMetaKey + 2 * fControlKey) { case 0: case 1: /* tab, alt-tab */ break; case 2: /* ctrl-tab */ fSkipSection = True; break; case 3: /* ctrl-meta-tab */ items_to_move = 5; break; default: break; } if (fShiftedKey) { /* shift reverses direction */ items_to_move = -items_to_move; } saction = SA_MOVE_ITEMS; break; case XK_Home: case XK_KP_7: saction = SA_FIRST; break; case XK_End: case XK_KP_1: saction = SA_LAST; break; #ifdef TEAR_OFF_MENUS case XK_BackSpace: fprintf(stderr,"menu torn off\n"); saction = SA_TEAROFF; break; #endif default: break; } if (!miCurrent && (saction == SA_ENTER || saction == SA_MOVE_ITEMS || saction == SA_SELECT)) { if (XGetGeometry(dpy, MR_WINDOW(mr), &JunkRoot, &menu_x, &menu_y, &menu_width, &menu_height, &JunkBW, &JunkDepth)) { XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &mx, &my, &JunkX, &JunkY, &JunkMask); if (my < menu_y + MST_BORDER_WIDTH(mr)) saction = SA_FIRST; else if (my > menu_y + menu_height - MST_BORDER_WIDTH(mr)) saction = SA_LAST; else saction = SA_WARPBACK; } else { saction = SA_FIRST; fvwm_msg( ERR, "menuShortcuts", "can't get geometry of menu %s", MR_NAME(mr)); } } /*** execute the necessary actions ***/ switch (saction) { case SA_ENTER: if (miCurrent && MI_IS_POPUP(miCurrent)) pmret->rc = MENU_POPUP; else pmret->rc = MENU_NOP; break; case SA_LEAVE: pmret->rc = MENU_POPDOWN; break; case SA_FIRST: if ((*pmiCurrent = get_selectable_item_from_index(mr,0)) != NULL) pmret->rc = MENU_NEWITEM; else pmret->rc = MENU_NOP; break; case SA_LAST: index = get_selectable_item_count(mr, NULL); if (index > 0) { *pmiCurrent = get_selectable_item_from_index(mr, index); pmret->rc = (*pmiCurrent) ? MENU_NEWITEM : MENU_NOP; } else { pmret->rc = MENU_NOP; } break; case SA_MOVE_ITEMS: /* Need isascii here - isgraph might coredump! */ if (isascii(keysym) && isgraph(keysym)) fControlKey = False; /* don't use control modifier for j or n, since those might be shortcuts too-- C-j, C-n will always work to do a single down */ if (fSkipSection) { int section; int count; get_selectable_item_count(mr, &count); get_selectable_item_index(mr, miCurrent, §ion); section += items_to_move; if (section < 0) section = count; else if (section > count) section = 0; index = section; } else if (items_to_move < 0) { index = get_selectable_item_index(mr, miCurrent, NULL); if (index == 0) /* wraparound */ index = get_selectable_item_count(mr, NULL); else { index += items_to_move; } } else { index = get_selectable_item_index(mr, miCurrent, NULL) + items_to_move; /* correct for the case that we're between items */ if (MI_IS_SEPARATOR(miCurrent) || MI_IS_TITLE(miCurrent)) index--; } if (fSkipSection) { newItem = get_selectable_item_from_section(mr, index); } else { newItem = get_selectable_item_from_index(mr, index); if (items_to_move > 0) { if (newItem == miCurrent) newItem = get_selectable_item_from_index(mr, 0); } } if (newItem) { *pmiCurrent = newItem; pmret->rc = MENU_NEWITEM; } else { pmret->rc = MENU_NOP; } break; case SA_CONTINUE: *pmiCurrent = MR_LAST_ITEM(mr); if (*pmiCurrent && MI_IS_POPUP(*pmiCurrent)) { /* enter the submenu */ pmret->rc = MENU_POPUP; } else { /* do nothing */ *pmiCurrent = miCurrent; pmret->rc = MENU_NOP; } break; case SA_WARPBACK: /* Warp the pointer back into the menu. */ XWarpPointer(dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0, menu_middle_x_offset(mr), my - menu_y); *pmiCurrent = find_entry(NULL, NULL, None, -1, -1); pmret->rc = MENU_NEWITEM; break; case SA_SELECT: pmret->rc = MENU_SELECTED; return; case SA_ABORT: pmret->rc = MENU_ABORTED; return; #ifdef TEAR_OFF_MENUS case SA_TEAROFF: pmret->rc = MENU_TEAR_OFF; return; #endif case SA_NONE: default: pmret->rc = MENU_NOP; break; } return; } static Bool is_double_click( Time t0, MenuItem *mi, MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp, Bool has_mouse_moved) { if (Event.type == KeyPress) return False; if (lastTimestamp - t0 >= MST_DOUBLE_CLICK_TIME(pmp->menu)) return False; if (has_mouse_moved) return False; if (!pmp->flags.has_default_action && (mi && mi == MR_FIRST_ITEM(pmp->menu) && MI_IS_SELECTABLE(mi))) return False; if (pmp->flags.is_submenu) return False; if (pmp->flags.is_invoked_by_key_press && pdkp->timestamp == 0) return False; return True; } /*********************************************************************** * * Procedure: * Interacts with user to Update menu display and start new submenus, * return to old sub menus, etc. * Input * sticks = 0, transient style menu, drops on button release * sticks = 1, sticky style, stays up on a click (i.e. initial release * comes soon after initial press. * Returns: * MENU_ERROR on error condition * MENU_SUBMENU_DONE on return from submenu to parent menu * MENU_DONE on completion by non-button-release (e.g. keyboard) * MENU_ABORT on abort of menu by non-button-release (e.g. keyboard) * MENU_SELECTED on item selection -- returns action * in *ret_paction * ***********************************************************************/ static void MenuInteraction( MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp, Bool *pdo_warp_to_title) { extern XEvent Event; MenuItem *mi = NULL; MenuItem *tmi; MenuItem *miRemovedSubmenu = NULL; MenuRoot *mrMi = NULL; MenuRoot *tmrMi = NULL; MenuRoot *mrPopup = NULL; MenuRoot *mrMiPopup = NULL; MenuRoot *mrNeedsPainting = NULL; MenuRoot *mrPopdown = NULL; int x_init = 0; int y_init = 0; int x_offset = 0; int popdown_delay_10ms = 0; int popup_delay_10ms = 0; Time t0 = lastTimestamp; MenuOptions mops; exec_func_args_type efa; struct { unsigned do_popup_immediately : 1; /* used for delay popups, to just popup the menu */ unsigned do_popup_now : 1; unsigned do_popdown_now : 1; /* used for keystrokes, to popup and move to that menu */ unsigned do_popup_and_warp : 1; unsigned do_force_reposition : 1; unsigned do_force_popup : 1; unsigned do_popdown : 1; unsigned do_popup : 1; unsigned do_menu : 1; unsigned do_recycle_event : 1; unsigned do_propagate_event_into_submenu : 1; unsigned has_mouse_moved : 1; unsigned is_off_menu_allowed : 1; unsigned is_key_press : 1; unsigned is_item_entered_by_key_press : 1; unsigned is_motion_faked : 1; unsigned is_popped_up_by_timeout : 1; unsigned is_pointer_in_active_item_area : 1; unsigned is_motion_first : 1; unsigned is_release_first : 1; unsigned is_submenu_mapped : 1; unsigned was_item_unposted : 1; } flags; /* Can't make this a member of the flags as we have to take its address. */ Bool does_submenu_overlap = False; Bool does_popdown_submenu_overlap = False; pmret->rc = MENU_NOP; memset(&flags, 0, sizeof(flags)); flags.do_force_reposition = 1; memset(&mops, 0, sizeof(mops)); /* remember where the pointer was so we can tell if it has moved */ XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &x_init, &y_init, &JunkX, &JunkY, &JunkMask); while (True) { flags.do_popup_and_warp = False; flags.do_popup_now = False; flags.do_popdown_now = False; flags.do_propagate_event_into_submenu = False; flags.is_key_press = False; if (pmp->event_propagate_to_submenu) { /* handle an event that was passed in from the parent menu */ memcpy(&Event, pmp->event_propagate_to_submenu, sizeof(XEvent)); pmp->event_propagate_to_submenu = NULL; } else if (flags.do_recycle_event) { flags.is_popped_up_by_timeout = False; flags.do_recycle_event = 0; if (pmp->menu != pmret->menu) { /* the event is for a previous menu, just close this one */ pmret->rc = MENU_PROPAGATE_EVENT; goto DO_RETURN; } if (Event.type == KeyPress) { /* since the pointer has been warped since the key was pressed, fake a * different key press position */ XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &Event.xkey.x_root, &Event.xkey.y_root, &JunkX, &JunkY, &JunkMask); mi = MR_SELECTED_ITEM(pmp->menu); } } /* flags.do_recycle_event */ else { if (flags.do_force_reposition) { Event.type = MotionNotify; Event.xmotion.time = lastTimestamp; flags.is_motion_faked = True; flags.do_force_reposition = False; flags.is_popped_up_by_timeout = False; } else if (!XCheckMaskEvent(dpy,ExposureMask,&Event)) { /* handle exposure events first */ if (flags.do_force_popup || flags.is_pointer_in_active_item_area || MST_POPDOWN_DELAY(pmp->menu) > 0 || (MST_POPUP_DELAY(pmp->menu) > 0 && !flags.is_popped_up_by_timeout)) { while (!XPending(dpy) || !XCheckMaskEvent(dpy, XEVMASK_MENU, &Event)) { Bool is_popup_timed_out = (MST_POPUP_DELAY(pmp->menu) > 0 && popup_delay_10ms++ >= MST_POPUP_DELAY(pmp->menu) + 1); Bool is_popdown_timed_out = (MST_POPDOWN_DELAY(pmp->menu) > 0 && mrPopdown && popdown_delay_10ms++ >= MST_POPDOWN_DELAY(pmp->menu) + 1); Bool do_fake_motion = False; if (is_popup_timed_out) { popup_delay_10ms = MST_POPUP_DELAY(pmp->menu); } if (is_popdown_timed_out) { popdown_delay_10ms = MST_POPDOWN_DELAY(pmp->menu); } if (flags.do_force_popup || (is_popup_timed_out && (is_popdown_timed_out || flags.is_item_entered_by_key_press || !mrPopdown || MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)))) { flags.do_popup_now = True; flags.do_force_popup = False; do_fake_motion = True; flags.is_popped_up_by_timeout = True; is_popdown_timed_out = True; } if ((mrPopdown || mrPopup) && (is_popdown_timed_out || MST_DO_POPDOWN_IMMEDIATELY(pmp->menu))) { MenuRoot *m = (mrPopdown) ? mrPopdown : mrPopup; if (!m || mi != MR_PARENT_ITEM(m)) { flags.do_popdown_now = True; do_fake_motion = True; if (MST_DO_POPUP_IMMEDIATELY(pmp->menu)) { flags.do_popup_now = True; flags.is_popped_up_by_timeout = True; } else if (!MST_DO_POPUP_IMMEDIATELY(pmp->menu) && flags.is_pointer_in_active_item_area) { flags.do_popup_now = True; flags.is_popped_up_by_timeout = True; } else if (!MST_DO_POPUP_IMMEDIATELY(pmp->menu) && !MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) && MST_POPUP_DELAY(pmp->menu) <= MST_POPDOWN_DELAY(pmp->menu) && popup_delay_10ms == popdown_delay_10ms) { flags.do_popup_now = True; flags.is_popped_up_by_timeout = True; } } } if (flags.do_popup_now && mi == miRemovedSubmenu && !flags.is_key_press) { /* prevent popping up the menu again with RemoveSubemenus */ flags.do_popup_now = False; flags.do_force_popup = False; do_fake_motion = flags.do_popdown_now; flags.is_popped_up_by_timeout = False; } if (do_fake_motion) { /* fake a motion event, and set flags.do_popup_now */ Event.type = MotionNotify; Event.xmotion.time = lastTimestamp; flags.is_motion_faked = True; break; } usleep(10000 /* 10 ms*/); } } else { /* block until there is an event */ XMaskEvent(dpy, XEVMASK_MENU, &Event); flags.is_popped_up_by_timeout = False; } } else { flags.is_popped_up_by_timeout = False; } } /* !flags.do_recycle_event */ flags.is_pointer_in_active_item_area = False; StashEventTime(&Event); if (Event.type == MotionNotify) { /* discard any extra motion events before a release */ while ((XCheckMaskEvent(dpy,ButtonMotionMask|ButtonReleaseMask, &Event))&&(Event.type != ButtonRelease)) { /* nop */ } } pmret->rc = MENU_NOP; switch(Event.type) { case ButtonRelease: mi = find_entry(&x_offset, &mrMi, None, -1, -1); /* hold the menu up when the button is released * for the first time if released OFF of the menu */ if(pmp->flags.is_sticky && !flags.is_motion_first) { flags.is_release_first = True; pmp->flags.is_sticky = False; continue; /* break; */ } flags.was_item_unposted = 0; if (pmret->flags.is_menu_posted && mrMi != NULL) { if (pmret->flags.do_unpost_submenu) { pmret->flags.do_unpost_submenu = 0; pmret->flags.is_menu_posted = 0; /* just ignore the event */ continue; } else if (mi && MI_IS_POPUP(mi) && mrMi == pmp->menu) { /* post menu - done below */ } else if (MR_PARENT_ITEM(pmp->menu) && mi == MR_PARENT_ITEM(pmp->menu)) { /* propagate back to parent menu and unpost current menu */ pmret->flags.do_unpost_submenu = 1; pmret->rc = MENU_PROPAGATE_EVENT; pmret->menu = mrMi; goto DO_RETURN; } else if (mrMi != pmp->menu && mrMi != mrPopup) { /* unpost and propagate back to ancestor */ pmret->flags.is_menu_posted = 0; pmret->rc = MENU_PROPAGATE_EVENT; pmret->menu = mrMi; goto DO_RETURN; } else if (mrPopup && mrMi == mrPopup) { /* unpost and propagate into submenu */ flags.was_item_unposted = 1; flags.do_propagate_event_into_submenu = True; break; } else { /* simply unpost the menu */ pmret->flags.is_menu_posted = 0; } } if (is_double_click(t0, mi, pmp, pmret, pdkp, flags.has_mouse_moved)) { pmret->rc = MENU_DOUBLE_CLICKED; goto DO_RETURN; } pmret->rc = (mi) ? MENU_SELECTED : MENU_ABORTED; if (pmret->rc == MENU_SELECTED && mi && MI_IS_POPUP(mi) && !MST_DO_POPUP_AS_ROOT_MENU(pmp->menu)) { if (flags.was_item_unposted) { pmret->flags.is_menu_posted = 0; pmret->rc = MENU_UNPOST; pmret->menu = NULL; pmret->parent_menu = pmp->menu; flags.do_popup_now = False; } else { pmret->flags.is_menu_posted = 1; pmret->rc = MENU_POST; pmret->menu = NULL; pmret->parent_menu = pmp->menu; flags.do_popup_now = True; if ((mrPopup || mrPopdown) && mi != MR_SELECTED_ITEM(pmp->menu)) { flags.do_popdown_now = True; } } break; } pdkp->timestamp = 0; goto DO_RETURN; case ButtonPress: /* if the first event is a button press allow the release to * select something */ pmp->flags.is_sticky = False; continue; case VisibilityNotify: continue; case KeyRelease: if (Event.xkey.keycode != MST_SELECT_ON_RELEASE_KEY(pmp->menu)) continue; /* fall through to KeyPress */ case KeyPress: /* Handle a key press events to allow mouseless operation */ flags.is_key_press = True; x_offset = menu_middle_x_offset(pmp->menu); /* if there is a posted menu we may have to move back into a previous * menu or possibly ignore the mouse position */ if (pmret->flags.is_menu_posted) { MenuItem *l_mi; MenuRoot *l_mrMi; int l_x_offset; pmret->flags.is_menu_posted = 0; l_mi = find_entry(&l_x_offset, &l_mrMi, None, -1, -1); if (l_mrMi != NULL) { if (pmp->menu != l_mrMi) { /* unpost the menu and propagate the event to the correct menu */ pmret->rc = MENU_PROPAGATE_EVENT; pmret->menu = l_mrMi; goto DO_RETURN; } } mi = MR_SELECTED_ITEM(pmp->menu); Event.xkey.x_root = x_offset; Event.xkey.y_root = item_middle_y_offset(pmp->menu, mi); } /* now handle the actual key press */ menuShortcuts(pmp->menu, pmp, pmret, &Event, &mi, pdkp); if (pmret->rc != MENU_NOP) { /* using a key 'unposts' the posted menu */ pmret->flags.is_menu_posted = 0; } switch (pmret->rc) { case MENU_SELECTED: if (mi && MI_IS_POPUP(mi) && !MST_DO_POPUP_AS_ROOT_MENU(pmp->menu)) { pmret->rc = MENU_POPUP; break; } goto DO_RETURN; case MENU_POPDOWN: case MENU_ABORTED: case MENU_DOUBLE_CLICKED: #ifdef TEAR_OFF_MENUS case MENU_TEAR_OFF: #endif goto DO_RETURN; case MENU_NEWITEM: case MENU_POPUP: if (mrMi == NULL) { /* Set the MenuRoot of the current item in case we have just warped to * the menu from the void or unposted a popup menu. */ mrMi = pmp->menu; } /*tmrMi = mrMi;*/ break; default: break; } /* now warp to the new menu item, if any */ if (pmret->rc == MENU_NEWITEM && mi) { warp_pointer_to_item(mrMi, mi, False); } if (pmret->rc == MENU_POPUP && mi && MI_IS_POPUP(mi)) { flags.do_popup_and_warp = True; break; } break; case MotionNotify: { int p_rx = -1; int p_ry = -1; Window p_child = None; if (flags.has_mouse_moved == False) { XQueryPointer(dpy, Scr.Root, &JunkRoot, &p_child, &p_rx, &p_ry, &JunkX, &JunkY, &JunkMask); if (p_rx - x_init > Scr.MoveThreshold || x_init - p_rx > Scr.MoveThreshold || p_ry - y_init > Scr.MoveThreshold || y_init - p_ry > Scr.MoveThreshold) { /* global variable remember that this isn't just * a click any more since the pointer moved */ flags.has_mouse_moved = True; } } mi = find_entry(&x_offset, &mrMi, p_child, p_rx, p_ry); if (pmret->flags.is_menu_posted && mrMi != pmret->menu) { /* ignore mouse movement outside a posted menu */ mrMi = NULL; mi = NULL; } if (!flags.is_release_first && !flags.is_motion_faked && flags.has_mouse_moved) flags.is_motion_first = True; flags.is_motion_faked = False; break; } case Expose: /* grab our expose events, let the rest go through */ if((XFindContext(dpy, Event.xany.window, MenuContext, (caddr_t *)&mrNeedsPainting) != XCNOENT)) { flush_accumulate_expose(Event.xany.window, &Event); paint_menu(mrNeedsPainting, &Event, (*pmp->pTmp_win)); } /* we want to dispatch this too so window decorations get redrawn * after being obscured by menus. */ /* We need to preserve the Tmp_win here! Note that handling an Expose * event will never invalidate the Tmp_win. */ DispatchEvent(True); continue; default: DispatchEvent(False); break; } /* switch (Event.type) */ /************************************************************************ * Now handle new menu items, whether it is from a keypress or a pointer * motion event. ************************************************************************/ pmret->flags.do_unpost_submenu = 0; if (mi) { /* we're on a menu item */ flags.is_off_menu_allowed = False; if (mrMi == pmp->menu && mi != miRemovedSubmenu) { miRemovedSubmenu = NULL; } if (mrMi != pmp->menu && mrMi != mrPopup && mrMi != mrPopdown) { /* we're on an item from a prior menu */ if (mrMi != MR_PARENT_MENU(pmp->menu)) { /* the event is for a previous menu, just close this one */ pmret->rc = MENU_PROPAGATE_EVENT; pmret->menu = mrMi; } else { pmret->rc = MENU_POPDOWN; } pdkp->timestamp = 0; goto DO_RETURN; } /* see if we're on a new item of this menu */ if (mi != MR_SELECTED_ITEM(pmp->menu) && mrMi == pmp->menu) { flags.is_item_entered_by_key_press = flags.is_key_press; popup_delay_10ms = 0; /* we're on the same menu, but a different item, * so we need to unselect the old item */ if (MR_SELECTED_ITEM(pmp->menu)) { /* something else was already selected on this menu */ /* We have to pop down the menu before unselecting the item in case * we are using gradient menus. The recalled image would paint over * the submenu. */ if (mrPopup && mrPopup != mrPopdown) { mrPopdown = mrPopup; popdown_delay_10ms = 0; does_popdown_submenu_overlap = does_submenu_overlap; mrPopup = NULL; } select_menu_item(pmp->menu, MR_SELECTED_ITEM(pmp->menu), False, (*pmp->pTmp_win)); } /* highlight the new item; sets MR_SELECTED_ITEM(pmp->menu), too */ select_menu_item(pmp->menu, mi, True, (*pmp->pTmp_win)); } /* new item of the same menu */ else if (mi != MR_SELECTED_ITEM(pmp->menu) && mrMi && mrMi == mrPopdown) { /* we're on the popup menu of a different menu item of this menu */ mi = MR_PARENT_ITEM(mrPopdown); mrPopup = mrPopdown; mrPopdown = NULL; does_submenu_overlap = does_popdown_submenu_overlap; select_menu_item(pmp->menu, mi, True, (*pmp->pTmp_win)); } mrMiPopup = mr_popup_for_mi(pmp->menu, mi); /* check what has to be done with the item */ flags.do_popdown = False; flags.do_popup = False; flags.do_menu = False; flags.is_submenu_mapped = False; if (!mrPopup && mrPopdown && mi == MR_PARENT_ITEM(mrPopdown)) { /* We're again on the item that we left before. Deschedule popping it * down. */ mrPopup = mrPopdown; mrPopdown = NULL; } if (mrMi == mrPopup) { /* must make current popup menu a real menu */ flags.do_menu = True; flags.is_submenu_mapped = True; } else if (pmret->rc == MENU_POST) { /* must create a real menu and warp into it */ if (mrPopup == NULL || mrPopup != mrMiPopup) { flags.do_popup = True; } flags.do_menu = True; flags.is_submenu_mapped = True; } else if (flags.do_popup_and_warp) { /* must create a real menu and warp into it */ if (mrPopup == NULL || mrPopup != mrMiPopup) { flags.do_popup = True; } else { XRaiseWindow(dpy, MR_WINDOW(mrPopup)); warp_pointer_to_item(mrPopup, MR_FIRST_ITEM(mrPopup), True); flags.do_menu = True; flags.is_submenu_mapped = True; } } else if (mi && MI_IS_POPUP(mi)) { if (Event.type == ButtonPress && is_double_click(t0, mi, pmp, pmret, pdkp, flags.has_mouse_moved)) { pmret->rc = MENU_DOUBLE_CLICKED; goto DO_RETURN; } else if (!mrPopup || mrPopup != mrMiPopup || flags.do_popup_and_warp) { Bool do_it_now = False; if (flags.do_popup_now) { do_it_now = True; } else if (MST_DO_POPUP_IMMEDIATELY(pmp->menu) && mi != miRemovedSubmenu) { if (flags.is_key_press || MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !mrPopdown) { do_it_now = True; } } else if (pointer_in_active_item_area(x_offset, mrMi)) { if (flags.is_key_press || mi == miRemovedSubmenu || MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !mrPopdown) { do_it_now = True; } else { flags.is_pointer_in_active_item_area = True; } } if (do_it_now) { miRemovedSubmenu = NULL; /* must create a new menu or popup */ if (mrPopup == NULL || mrPopup != mrMiPopup) { if (flags.do_popup_now) flags.do_popup = True; else { /* pop up in next pass through loop */ flags.do_force_popup = True; } } else if (flags.do_popup_and_warp) { warp_pointer_to_item(mrPopup, MR_FIRST_ITEM(mrPopup), True); } } } } /* else if (mi && MI_IS_POPUP(mi)) */ if (flags.do_popdown_now) { flags.do_popdown = True; flags.do_popdown_now = False; } else if (flags.do_popup) { if (mrPopup && mrPopup != mrMiPopup) { flags.do_popdown = True; mrPopdown = mrPopup; popdown_delay_10ms = 0; does_popdown_submenu_overlap = does_submenu_overlap; } else if (mrPopdown && mrPopdown != mrMiPopup) { flags.do_popdown = True; } else if (mrPopdown && mrPopdown == mrMiPopup) { mrPopup = mrPopdown; does_submenu_overlap = does_popdown_submenu_overlap; mrPopdown = NULL; flags.do_popup = False; flags.do_popdown = False; } } if (flags.do_popdown && !flags.do_popup) { /* popdown previous popup */ if (mrPopdown) { pop_menu_down_and_repaint_parent( &mrPopdown, &does_popdown_submenu_overlap, pmp); MR_SUBMENU_ITEM(pmp->menu) = NULL; if (mrPopup == mrPopdown) mrPopup = NULL; mrPopdown = NULL; } flags.do_popdown = False; } /* if (flags.do_popdown && !flags.do_popup) */ if (flags.do_popup) { if (!MR_IS_PAINTED(pmp->menu)) { /* draw the parent menu if it is not already drawn */ flush_expose(MR_WINDOW(pmp->menu)); paint_menu(pmp->menu, NULL, (*pmp->pTmp_win)); } /* get pos hints for item's action */ get_popup_options(pmp, mi, &mops); if (mrMi == pmp->menu && mrMiPopup == NULL && MI_IS_POPUP(mi) && MR_MISSING_SUBMENU_FUNC(pmp->menu)) { /* We're on a submenu item, but the submenu does not exist. The * user defined missing_submenu_action may create it. */ Bool is_complex_function; Bool f; Bool is_busy_grabbed = False; Time t; char *menu_name; char *action; extern XEvent Event; char *missing_action = MR_MISSING_SUBMENU_FUNC(pmp->menu); f = *pdo_warp_to_title; t = lastTimestamp; menu_name = PeekToken(SkipNTokens(MI_ACTION(mi), 1), NULL); if (!menu_name) { menu_name = ""; } is_complex_function = (FindFunction(missing_action) != NULL); if (is_complex_function) { char *action_ptr; action = safemalloc( strlen("Function") + 3 + strlen(missing_action) * 2 + 3 + strlen(menu_name) * 2 + 1); strcpy(action, "Function "); action_ptr = action + strlen(action); action_ptr = QuoteString(action_ptr, missing_action); *action_ptr++ = ' '; action_ptr = QuoteString(action_ptr, menu_name); } else { action = MR_MISSING_SUBMENU_FUNC(pmp->menu); } if (Scr.BusyCursor & BUSY_DYNAMICMENU) is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU); /* Execute the action */ memset(&efa, 0, sizeof(efa)); efa.eventp = &Event; efa.tmp_win = *(pmp->pTmp_win); efa.action = action; efa.context = *(pmp->pcontext); efa.module = -2; efa.flags.exec = FUNC_DONT_EXPAND_COMMAND; efa.flags.do_save_tmpwin = 1; execute_function(&efa); if (is_complex_function) free(action); if (is_busy_grabbed) UngrabEm(GRAB_BUSYMENU); /* restore the stuff we saved */ lastTimestamp = t; *pdo_warp_to_title = f; if (!check_if_fvwm_window_exists(*(pmp->pTmp_win))) { *(pmp->pTmp_win) = NULL; *(pmp->pcontext) = 0; } /* Let's see if the menu exists now. */ mrMiPopup = mr_popup_for_mi(pmp->menu, mi); } /* run MISSING_SUBMENU_FUNCTION */ mrPopup = mrMiPopup; if (!mrPopup) { flags.do_menu = False; pmret->flags.is_menu_posted = 0; } else { /* create a popup menu */ if (!is_submenu_mapped(pmp->menu, mi)) { /* We want to pop prepop menus so it doesn't *have* to be unpopped; do_menu pops down any menus it pops up, but we want to be able to popdown w/o actually removing the menu */ int x; int y; Bool prefer_left_submenus; /* Make sure we are using the latest style and menu layout. */ update_menu(mrPopup, pmp); get_prefered_popup_position( pmp->menu, mrPopup, &x, &y, &prefer_left_submenus); /* Note that we don't care if popping up the menu works. If it * doesn't we'll catch it below. */ pop_menu_up( &mrPopup, pmp, pmp->menu, mi, pmp->pTmp_win, pmp->pcontext, x, y, prefer_left_submenus, flags.do_popup_and_warp, &mops, &does_submenu_overlap, pdo_warp_to_title, (mrPopdown) ? MR_WINDOW(mrPopdown) : None); if (mrPopup == NULL) { /* the menu deleted itself when execution the dynamic popup * action */ pmret->rc = MENU_ERROR; goto DO_RETURN; } MR_SUBMENU_ITEM(pmp->menu) = mi; } } /* else (mrPopup) */ if (flags.do_popdown) { if (mrPopdown) { if (mrPopdown != mrPopup) { pop_menu_down_and_repaint_parent( &mrPopdown, &does_popdown_submenu_overlap, pmp); } mrPopdown = NULL; } flags.do_popdown = False; } if (mrPopup) { if (MR_PARENT_MENU(mrPopup) == pmp->menu) { mi = find_entry(NULL, &mrMi, None, -1, -1); if (mi && mrMi == mrPopup) { flags.do_menu = True; flags.is_submenu_mapped = True; } } else { /* This menu must be already mapped somewhere else, so ignore * it completely. This can only happen if we have reached the * maximum allowed number of menu copies. */ flags.do_menu = False; flags.do_popdown = False; mrPopup = NULL; } } /* if (mrPopup) */ } /* if (flags.do_popup) */ /* remember the 'posted' menu */ if (pmret->flags.is_menu_posted && mrPopup != NULL && pmret->menu == NULL) { pmret->menu = mrPopup; } if (flags.do_menu) { { MenuParameters mp; XEvent e; memset(&mp, 0, sizeof(mp)); mp.menu = mrPopup; mp.parent_menu = pmp->menu; mp.parent_item = mi; mp.pTmp_win = pmp->pTmp_win; mp.button_window = pmp->button_window; mp.pcontext = pmp->pcontext; mp.flags.has_default_action = False; mp.flags.is_menu_from_frame_or_window_or_titlebar = False; mp.flags.is_sticky = False; mp.flags.is_submenu = True; mp.flags.is_already_mapped = flags.is_submenu_mapped; mp.eventp = (flags.do_popup_and_warp) ? (XEvent *)1 : NULL; mp.pops = &mops; mp.ret_paction = pmp->ret_paction; mp.screen_origin_x = pmp->screen_origin_x; mp.screen_origin_y = pmp->screen_origin_y; if (flags.do_propagate_event_into_submenu) { memcpy(&e, &Event, sizeof(XEvent)); mp.event_propagate_to_submenu = &e; } else { mp.event_propagate_to_submenu = NULL; } /* recursively do the new menu we've moved into */ do_menu(&mp, pmret); } flags.do_propagate_event_into_submenu = False; if (pmret->rc == MENU_PROPAGATE_EVENT) { flags.do_recycle_event = 1; continue; } if (IS_MENU_RETURN(pmret->rc)) { pdkp->timestamp = 0; goto DO_RETURN; } if (MST_DO_UNMAP_SUBMENU_ON_POPDOWN(pmp->menu) && pmret->flags.is_key_press) { miRemovedSubmenu = MR_PARENT_ITEM(mrPopup); pop_menu_down_and_repaint_parent( &mrPopup, &does_submenu_overlap, pmp); MR_SUBMENU_ITEM(pmp->menu) = NULL; if (mrPopup == mrPopdown) mrPopdown = NULL; mrPopup = NULL; } if (pmret->rc == MENU_POPDOWN) { popup_delay_10ms = 0; flags.do_force_reposition = True; } } /* if (flags.do_menu) */ /* Now check whether we can animate the current popup menu back to the * original place to unobscure the current menu; this happens only when * using animation */ if (mrPopup && MR_XANIMATION(mrPopup) && (tmi = find_entry(NULL, &tmrMi, None, -1, -1)) && (tmi == MR_SELECTED_ITEM(pmp->menu) || tmrMi != pmp->menu)) { animated_move_back(mrPopup, False); } /* now check whether we should animate the current real menu over to the right to unobscure the prior menu; only a very limited case where this might be helpful and not too disruptive */ if (mrPopup == NULL && pmp->parent_menu != NULL && MR_XANIMATION(pmp->menu) != 0 && pointer_in_passive_item_area(x_offset, mrMi)) { /* we have to see if we need menu to be moved */ animated_move_back(pmp->menu, True); if (mrPopdown) { if (mrPopdown != mrPopup) { pop_menu_down_and_repaint_parent( &mrPopdown, &does_popdown_submenu_overlap, pmp); } mrPopdown = NULL; } flags.do_popdown = False; } } /* if (mi) */ else { /* moved off menu, deselect selected item... */ if (MR_SELECTED_ITEM(pmp->menu) && flags.is_off_menu_allowed == False && !pmret->flags.is_menu_posted) { if (mrPopup) { int x, y, mx, my; unsigned int mw, mh; XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &x, &y, &JunkX, &JunkY, &JunkMask); if (XGetGeometry(dpy, MR_WINDOW(pmp->menu), &JunkRoot, &mx, &my, &mw, &mh, &JunkBW, &JunkDepth) && ((!MR_IS_LEFT(mrPopup) && x < mx) || (!MR_IS_RIGHT(mrPopup) && x > mx + mw) || (!MR_IS_UP(mrPopup) && y < my) || (!MR_IS_DOWN(mrPopup) && y > my + mh))) { select_menu_item( pmp->menu, MR_SELECTED_ITEM(pmp->menu), False, (*pmp->pTmp_win)); pop_menu_down_and_repaint_parent( &mrPopup, &does_submenu_overlap, pmp); MR_SUBMENU_ITEM(pmp->menu) = NULL; if (mrPopup == mrPopdown) mrPopdown = NULL; mrPopup = NULL; } else if (x < mx || x >= mx + mw || y < my || y >= my + mh) { /* pointer is outside the menu but do not pop down */ flags.is_off_menu_allowed = True; } else { /* Pointer is still in the menu. Postpone the decision if we have * to pop down. */ } } /* if (mrPopup) */ else { select_menu_item(pmp->menu, MR_SELECTED_ITEM(pmp->menu), False, (*pmp->pTmp_win)); } } /* if (MR_SELECTED_ITEM(mpmp->enu)) */ } /* else (!mi) */ XFlush(dpy); } /* while (True) */ DO_RETURN: if (mrPopdown) { pop_menu_down_and_repaint_parent( &mrPopdown, &does_popdown_submenu_overlap, pmp); MR_SUBMENU_ITEM(pmp->menu) = NULL; } if (pmret->rc == MENU_SELECTED && is_double_click(t0, mi, pmp, pmret, pdkp, flags.has_mouse_moved)) { pmret->rc = MENU_DOUBLE_CLICKED; } switch (pmret->rc) { case MENU_POPDOWN: case MENU_PROPAGATE_EVENT: case MENU_DOUBLE_CLICKED: if (MR_SELECTED_ITEM(pmp->menu)) select_menu_item(pmp->menu, MR_SELECTED_ITEM(pmp->menu), False, (*pmp->pTmp_win)); if (flags.is_key_press && pmret->rc != MENU_DOUBLE_CLICKED) { if (!pmp->flags.is_submenu) { /* abort a root menu rather than pop it down */ pmret->rc = MENU_ABORTED; } if (pmp->parent_menu && MR_SELECTED_ITEM(pmp->parent_menu)) { warp_pointer_to_item(pmp->parent_menu, MR_SELECTED_ITEM(pmp->parent_menu), False); tmi = find_entry(NULL, &tmrMi, None, -1, -1); if (pmp->parent_menu != tmrMi && MR_XANIMATION(pmp->menu) == 0) { /* Warping didn't take us to the correct menu, i.e. the spot we want * to warp to is obscured. So raise our window first. */ XRaiseWindow(dpy, MR_WINDOW(pmp->parent_menu)); } } } break; case MENU_SELECTED: /* save action to execute so that the menu may be destroyed now */ if (pmp->ret_paction) *pmp->ret_paction = (mi) ? safestrdup(MI_ACTION(mi)) : NULL; pmret->rc = MENU_DONE; if (pmp->ret_paction && *pmp->ret_paction && mi && MI_IS_POPUP(mi)) { get_popup_options(pmp, mi, &mops); if (mops.flags.do_select_in_place) { MenuRoot *submenu; XWindowAttributes win_attribs; last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = True; last_saved_pos_hints.pos_hints.x_offset = 0; last_saved_pos_hints.pos_hints.x_factor = 0; last_saved_pos_hints.pos_hints.context_x_factor = 0; last_saved_pos_hints.pos_hints.y_factor = 0; last_saved_pos_hints.pos_hints.is_relative = False; last_saved_pos_hints.pos_hints.is_menu_relative = False; submenu = mr_popup_for_mi(pmp->menu, mi); if (submenu && MR_WINDOW(submenu) != None && XGetWindowAttributes(dpy, MR_WINDOW(submenu), &win_attribs) && win_attribs.map_state == IsViewable && XGetGeometry(dpy, MR_WINDOW(submenu), &JunkRoot, &last_saved_pos_hints.pos_hints.x, &last_saved_pos_hints.pos_hints.y, &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth)) { /* The submenu is mapped, just take its position and put it in the * position hints. */ } else if (mops.flags.has_poshints) { last_saved_pos_hints.pos_hints = mops.pos_hints; } else { Bool dummy; get_prefered_popup_position( pmp->menu, submenu, &last_saved_pos_hints.pos_hints.x, &last_saved_pos_hints.pos_hints.y, &dummy); } if (mops.flags.do_warp_on_select) { *pdo_warp_to_title = True; } } /* select in place */ else { last_saved_pos_hints.flags.do_ignore_pos_hints = True; } /* mops.flags.do_select_in_place */ last_saved_pos_hints.pos_hints.screen_origin_x = pmp->screen_origin_x; last_saved_pos_hints.pos_hints.screen_origin_y = pmp->screen_origin_y; } /* a menu was selected */ break; #ifdef TEAR_OFF_MENUS case MENU_TEAR_OFF: fprintf(stderr,"got MENU_TEAR_OFF\n"); AddWindow(MR_WINDOW(pmp->menu), NULL); break; #endif default: break; } if (mrPopup) { pop_menu_down_and_repaint_parent(&mrPopup, &does_submenu_overlap, pmp); MR_SUBMENU_ITEM(pmp->menu) = NULL; } pmret->flags.is_key_press = flags.is_key_press; return; } static int do_menus_overlap( MenuRoot *mr, int x, int y, int width, int height, int h_tolerance, int v_tolerance, int s_tolerance, Bool allow_popup_offset_tolerance) { int prior_x, prior_y, x_overlap; unsigned int prior_width, prior_height; if (mr == NULL) return 0; if (!XGetGeometry(dpy,MR_WINDOW(mr), &JunkRoot,&prior_x,&prior_y, &prior_width,&prior_height, &JunkBW, &JunkDepth)) { return 0; } x_overlap = 0; if (allow_popup_offset_tolerance) { if (MST_POPUP_OFFSET_ADD(mr) < 0) s_tolerance = -MST_POPUP_OFFSET_ADD(mr); if (MST_USE_LEFT_SUBMENUS(mr)) prior_x += (100 - MST_POPUP_OFFSET_PERCENT(mr)) / 100; else prior_width *= (float)(MST_POPUP_OFFSET_PERCENT(mr)) / 100.0; } if (MST_USE_LEFT_SUBMENUS(mr)) { int t = s_tolerance; s_tolerance = h_tolerance; h_tolerance = t; } if (y < prior_y + prior_height - v_tolerance && prior_y < y + height - v_tolerance && x < prior_x + prior_width - s_tolerance && prior_x < x + width - h_tolerance) { x_overlap = x - prior_x; if (x <= prior_x) { x_overlap--; } } return x_overlap; } /*********************************************************************** * * Procedure: * pop_menu_up - pop up a pull down menu * * Inputs: * x, y - location of upper left of menu * do_warp_to_item - warp pointer to the first item after title * pops - pointer to the menu options for new menu * ***********************************************************************/ static int pop_menu_up( MenuRoot **pmenu, MenuParameters *pmp, MenuRoot *parent_menu, MenuItem *parent_item, FvwmWindow **pfw, int *pcontext, int x, int y, Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops, Bool *ret_overlap, Bool *pdo_warp_to_title, Window popdown_window) { Bool do_warp_to_title = False; int x_overlap = 0; int x_clipped_overlap; MenuRoot *mrMi; MenuRoot *mr; FvwmWindow *fw; int context; int bw = 0; int bwp = 0; int prev_x; int prev_y; unsigned int prev_width; unsigned int prev_height; int scr_x, scr_y, scr_w, scr_h; mr = *pmenu; if (!mr || (MR_MAPPED_COPIES(mr) > 0 && MR_COPIES(mr) >= MAX_MENU_COPIES)) { *pdo_warp_to_title = False; return False; } /*************************************************************** * handle dynamic menu actions ***************************************************************/ /* First of all, execute the popup action (if defined). */ if (MR_POPUP_ACTION(mr)) { char *menu_name; saved_pos_hints pos_hints; Bool f; Bool is_busy_grabbed = False; Time t; exec_func_args_type efa; extern XEvent Event; int mapped_copies = MR_MAPPED_COPIES(mr); /* save variables that we still need but that may be overwritten */ menu_name = safestrdup(MR_NAME(mr)); pos_hints = last_saved_pos_hints; f = *pdo_warp_to_title; t = lastTimestamp; if (Scr.BusyCursor & BUSY_DYNAMICMENU) is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU); /* Execute the action */ memset(&efa, 0, sizeof(efa)); efa.eventp = &Event; efa.tmp_win = *pfw; efa.action = MR_POPUP_ACTION(mr); efa.context = *pcontext; efa.module = -2; efa.flags.do_save_tmpwin = 1; efa.flags.exec = FUNC_DONT_EXPAND_COMMAND; execute_function(&efa); if (is_busy_grabbed) UngrabEm(GRAB_BUSYMENU); /* restore the stuff we saved */ lastTimestamp = t; *pdo_warp_to_title = f; last_saved_pos_hints = pos_hints; /* See if the window has been deleted */ if (!check_if_fvwm_window_exists(*pfw)) { *pfw = NULL; *pcontext = 0; } if (mapped_copies == 0) { /* Now let's see if the menu still exists. It may have been destroyed and * recreated, so we have to look for a menu with the saved name. * FindPopup() will always return the original menu, not one of its * copies, so below logic would fail miserably if used with a menu copy. * On the other hand, menu copies can't be deleted within a dynamic popup * action, so just ignore this case. */ *pmenu = FindPopup(menu_name); if (menu_name) { free(menu_name); } mr = *pmenu; #if 0 if (mr) { make_menu(mr, pmp); } #endif } } #if 1 if (mr) { update_menu(mr, pmp); } #endif if (mr == NULL || MR_FIRST_ITEM(mr) == NULL || MR_ITEMS(mr) == 0) { /* The menu deleted itself or all its items or it has been empty from the * start. */ *pdo_warp_to_title = False; return False; } fw = *pfw; if (fw && !check_if_fvwm_window_exists(fw)) fw = NULL; context = *pcontext; /*************************************************************** * Create a new menu instance (if necessary) ***************************************************************/ if (MR_MAPPED_COPIES(mr) > 0) { /* create a new instance of the menu */ *pmenu = copy_menu_root(*pmenu); if (!*pmenu) return False; make_menu_window(*pmenu); mr = *pmenu; } /*************************************************************** * Evaluate position hints ***************************************************************/ /* calculate position from position hints if available */ if (pops->flags.has_poshints && !last_saved_pos_hints.flags.do_ignore_pos_hints) { get_xy_from_position_hints( &(pops->pos_hints), MR_WIDTH(mr), MR_HEIGHT(mr), (parent_menu) ? MR_WIDTH(parent_menu) : 0, prefer_left_submenu, &x, &y); } /*************************************************************** * Initialise new menu ***************************************************************/ MR_PARENT_MENU(mr) = parent_menu; MR_PARENT_ITEM(mr) = parent_item; MR_IS_PAINTED(mr) = 0; MR_IS_LEFT(mr) = 0; MR_IS_RIGHT(mr) = 0; MR_IS_UP(mr) = 0; MR_IS_DOWN(mr) = 0; MR_XANIMATION(mr) = 0; InstallFvwmColormap(); /*************************************************************** * Handle popups from button clicks on buttons in the title bar, * or the title bar itself. Position hints override this. ***************************************************************/ if (!pops->flags.has_poshints && fw && parent_menu == NULL && pmp->flags.is_first_root_menu) { int old_y = y; int cx; int cy; Bool has_context; if (HAS_BOTTOM_TITLE(fw)) { y = fw->frame_g.y + fw->frame_g.height - fw->boundary_width - fw->title_g.height - MR_HEIGHT(mr); cy = fw->frame_g.y + fw->frame_g.height - fw->boundary_width - fw->title_g.height / 2; } else { y = fw->frame_g.y + fw->boundary_width + fw->title_g.height; cy = y - fw->title_g.height / 2; } has_context = 0; if (context&C_LALL) { x = fw->frame_g.x + fw->boundary_width + ButtonPosition(context, fw) * fw->title_g.height; cx = x + fw->title_g.height / 2; has_context = 1; } else if (context&C_RALL) { x = fw->frame_g.x + fw->frame_g.width - fw->boundary_width - ButtonPosition(context, fw) * fw->title_g.height - MR_WIDTH(mr); cx = x + fw->title_g.height / 2; has_context = 1; } else if (context&C_TITLE) { if (x < fw->frame_g.x + fw->title_g.x) { x = fw->frame_g.x + fw->title_g.x; } if (x + MR_WIDTH(mr) > fw->frame_g.x + fw->title_g.x + fw->title_g.width) { x = fw->frame_g.x + fw->title_g.x + fw->title_g.width - MR_WIDTH(mr); } cx = x; has_context = 1; } else { y = old_y; } if (has_context) { fscreen_scr_arg fscr; pops->pos_hints.has_screen_origin = True; fscr.xypos.x = cx; fscr.xypos.y = cy; if (FScreenGetScrRect( &fscr, FSCREEN_XYPOS, &JunkX, &JunkY, &JunkWidth, &JunkHeight)) { /* use current cx/cy */ } else if (XQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild, &cx, &cy, &JunkX, &JunkY, &JunkMask)) { /* use pointer's position */ } else { cx = -1; cy = -1; } pops->pos_hints.screen_origin_x = cx; pops->pos_hints.screen_origin_y = cy; } } /* if (pops->flags.has_poshints) */ /*************************************************************** * Clip to screen ***************************************************************/ { fscreen_scr_arg fscr; fscr.xypos.x = pops->pos_hints.screen_origin_x; fscr.xypos.y = pops->pos_hints.screen_origin_y; /* clip to screen */ FScreenClipToScreen( &fscr, FSCREEN_XYPOS, &x, &y, MR_WIDTH(mr), MR_HEIGHT(mr)); /* "this" screen is defined -- so get its coords for future reference */ FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &scr_x, &scr_y, &scr_w, &scr_h); } if (parent_menu) { bw = MST_BORDER_WIDTH(mr); bwp = MST_BORDER_WIDTH(parent_menu); x_overlap = do_menus_overlap(parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), bwp, bwp, bwp, True); } /*************************************************************** * Calculate position and animate menus ***************************************************************/ if (parent_menu == NULL || !XGetGeometry(dpy, MR_WINDOW(parent_menu), &JunkRoot, &prev_x, &prev_y, &prev_width,&prev_height, &JunkBW, &JunkDepth)) { MR_HAS_POPPED_UP_LEFT(mr) = 0; MR_HAS_POPPED_UP_RIGHT(mr) = 0; } else { int left_x; int right_x; Bool use_left_submenus = MST_USE_LEFT_SUBMENUS(mr); /* check if menus overlap */ x_clipped_overlap = do_menus_overlap(parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), bwp, bwp, bwp, True); if (x_clipped_overlap && (!pops->flags.has_poshints || pops->pos_hints.is_relative == False || x_overlap == 0)) { /* menus do overlap, but do not reposition if overlap was caused by * relative positioning hints */ left_x = get_left_popup_x_position(parent_menu, mr, prev_x); right_x = get_right_popup_x_position(parent_menu, mr, prev_x); if (use_left_submenus) { if (left_x + MR_WIDTH(mr) < prev_x + bwp) left_x = prev_x + bwp - MR_WIDTH(mr); } else { if (right_x > prev_x + prev_width - bwp) right_x = prev_x + prev_width - bwp; } /* * Animate parent menu */ if (MST_IS_ANIMATED(mr)) { /* animate previous out of the way */ int a_left_x; int a_right_x; int end_x; if (use_left_submenus) { if (prev_x - left_x < MR_WIDTH(mr)) a_right_x = x + (prev_x - left_x); else a_right_x = x + MR_WIDTH(mr) - bw; a_left_x = x - MR_WIDTH(parent_menu); } else { if (right_x - prev_x < prev_width) a_left_x = x + (prev_x - right_x); else a_left_x = x - prev_width + bw; a_right_x = x + MR_WIDTH(mr); } if (prefer_left_submenu) { /* popup menu is left of old menu, try to move prior menu right */ if (a_right_x + prev_width <= scr_x + scr_w) end_x = a_right_x; else if (a_left_x >= scr_x) end_x = a_left_x; else end_x = scr_x + scr_w - prev_width; } else { /* popup menu is right of old menu, try to move prior menu left */ if (a_left_x >= scr_x) end_x = a_left_x; else if (a_right_x + prev_width <= scr_x + scr_w) end_x = a_right_x; else end_x = scr_x; } if (end_x == a_left_x || end_x == 0) { MR_HAS_POPPED_UP_LEFT(mr) = 0; MR_HAS_POPPED_UP_RIGHT(mr) = 1; } else { MR_HAS_POPPED_UP_LEFT(mr) = 1; MR_HAS_POPPED_UP_RIGHT(mr) = 0; } MR_XANIMATION(parent_menu) += end_x - prev_x; AnimatedMoveOfWindow(MR_WINDOW(parent_menu),prev_x,prev_y,end_x,prev_y, True, -1, NULL); } /* if (MST_IS_ANIMATED(mr)) */ /* * Try the other side of the parent menu */ else if (!pops->flags.is_fixed) { Bool may_use_left; Bool may_use_right; Bool do_use_left; Bool use_left_as_last_resort; use_left_as_last_resort = (left_x > scr_x + scr_w - right_x - MR_WIDTH(mr)); may_use_left = (left_x >= scr_x); may_use_right = (right_x + MR_WIDTH(mr) <= scr_x + scr_w); if (!may_use_left && !may_use_right) { /* If everything goes wrong, put the submenu on the side with more * free space. */ do_use_left = use_left_as_last_resort; } else if (may_use_left && (prefer_left_submenu || !may_use_right)) do_use_left = True; else do_use_left = False; x = (do_use_left) ? left_x : right_x; MR_HAS_POPPED_UP_LEFT(mr) = do_use_left; MR_HAS_POPPED_UP_RIGHT(mr) = !do_use_left; /* Force the menu onto the screen, but leave at least * PARENT_MENU_FORCE_VISIBLE_WIDTH pixels of the parent menu visible */ if (x + MR_WIDTH(mr) > scr_x + scr_w) { int d = x + MR_WIDTH(mr) - (scr_x + scr_w); int c; if (prev_width >= PARENT_MENU_FORCE_VISIBLE_WIDTH) c = prev_x + PARENT_MENU_FORCE_VISIBLE_WIDTH; else c = prev_x + prev_width; if (x - c >= d || x <= prev_x) x -= d; else if (x > c) x = c; } if (x < scr_x) { int c = prev_width - PARENT_MENU_FORCE_VISIBLE_WIDTH; if (c < 0) c = 0; if (scr_x - x > c) x += c; else x = scr_x; } } /* else if (non-overlapping menu style) */ } /* if (x_clipped_overlap && ...) */ else { MR_HAS_POPPED_UP_LEFT(mr) = prefer_left_submenu; MR_HAS_POPPED_UP_RIGHT(mr) = !prefer_left_submenu; } if (x < prev_x) MR_IS_LEFT(mr) = 1; if (x + MR_WIDTH(mr) > prev_x + prev_width) MR_IS_RIGHT(mr) = 1; if (y < prev_y) MR_IS_UP(mr) = 1; if (y + MR_HEIGHT(mr) > prev_y + prev_height) MR_IS_DOWN(mr) = 1; if (!MR_IS_LEFT(mr) && !MR_IS_RIGHT(mr)) { MR_IS_LEFT(mr) = 1; MR_IS_RIGHT(mr) = 1; } } /* if (parent_menu) */ /*************************************************************** * Pop up the menu ***************************************************************/ XMoveWindow(dpy, MR_WINDOW(mr), x, y); XMapRaised(dpy, MR_WINDOW(mr)); if (popdown_window) XUnmapWindow(dpy, popdown_window); XFlush(dpy); MR_MAPPED_COPIES(mr)++; MST_USAGE_COUNT(mr)++; if (ret_overlap) { *ret_overlap = do_menus_overlap(parent_menu, x, y, MR_WIDTH(mr), MR_HEIGHT(mr), 0, 0, 0, False) ? True : False; } /*************************************************************** * Warp the pointer ***************************************************************/ if (!do_warp_to_item) { MenuItem *mi; mi = find_entry(NULL, &mrMi, None, -1, -1); if (mi && mrMi == mr && mi != MR_FIRST_ITEM(mrMi)) { /* pointer is on an item of the popup */ if (MST_DO_WARP_TO_TITLE(mr)) { /* warp pointer if not on a root menu */ do_warp_to_title = True; } } } /* if (!do_warp_to_item) */ if (pops->flags.do_not_warp) { do_warp_to_title = False; } else if (pops->flags.do_warp_title) { do_warp_to_title = True; } if (*pdo_warp_to_title) { do_warp_to_title = True; *pdo_warp_to_title = False; } if (do_warp_to_item) { /* also warp */ MR_SELECTED_ITEM(mr) = NULL; warp_pointer_to_item(mr, MR_FIRST_ITEM(mr), True /* skip Title */); select_menu_item(mr, MR_SELECTED_ITEM(mr), True, fw); } else if(do_warp_to_title) { /* Warp pointer to middle of top line, since we don't * want the user to come up directly on an option */ warp_pointer_to_title(mr); } return True; } /* Set the selected-ness state of the menuitem passed in */ static void select_menu_item( MenuRoot *mr, MenuItem *mi, Bool select, FvwmWindow *fw) { if (select == True && MR_SELECTED_ITEM(mr) != NULL && MR_SELECTED_ITEM(mr) != mi) { select_menu_item(mr, MR_SELECTED_ITEM(mr), False, fw); } else if (select == False && MR_SELECTED_ITEM(mr) == NULL) return; else if (select == True && MR_SELECTED_ITEM(mr) == mi) return; if (!MI_IS_SELECTABLE(mi)) return; if (select == False) MI_WAS_DESELECTED(mi) = True; if (!MST_HAS_MENU_CSET(mr)) { switch (MST_FACE(mr).type) { case GradientMenu: if (select == True) { int iy; int ih; unsigned int mw; XEvent e; if (!MR_IS_PAINTED(mr)) { flush_expose(MR_WINDOW(mr)); paint_menu(mr, NULL, fw); } iy = MI_Y_OFFSET(mi); ih = MI_HEIGHT(mi) + (MI_IS_SELECTABLE(mi) ? MST_RELIEF_THICKNESS(mr) : 0); if (iy < 0) { ih += iy; iy = 0; } mw = MR_WIDTH(mr) - 2 * MST_BORDER_WIDTH(mr); if (iy + ih > MR_HEIGHT(mr)) ih = MR_HEIGHT(mr) - iy; /* grab image */ MR_STORED_ITEM(mr).stored = XCreatePixmap(dpy, Scr.NoFocusWin, mw, ih, Pdepth); XSetGraphicsExposures(dpy, MST_MENU_GC(mr), True); XSync(dpy, 0); while (XCheckTypedEvent(dpy, NoExpose, &e)) ; XCopyArea( dpy, MR_WINDOW(mr), MR_STORED_ITEM(mr).stored, MST_MENU_GC(mr), MST_BORDER_WIDTH(mr), iy, mw, ih, 0, 0); XSync(dpy, 0); if (XCheckTypedEvent(dpy, NoExpose, &e)) { MR_STORED_ITEM(mr).y = iy; MR_STORED_ITEM(mr).width = mw; MR_STORED_ITEM(mr).height = ih; } else { XFreePixmap(dpy, MR_STORED_ITEM(mr).stored); MR_STORED_ITEM(mr).stored = None; MR_STORED_ITEM(mr).width = 0; MR_STORED_ITEM(mr).height = 0; MR_STORED_ITEM(mr).y = 0; } XSetGraphicsExposures(dpy, MST_MENU_GC(mr), False); } else if (select == False && MR_STORED_ITEM(mr).width != 0) { /* ungrab image */ XCopyArea(dpy, MR_STORED_ITEM(mr).stored, MR_WINDOW(mr), MST_MENU_GC(mr), 0, 0, MR_STORED_ITEM(mr).width, MR_STORED_ITEM(mr).height, MST_BORDER_WIDTH(mr), MR_STORED_ITEM(mr).y); if (MR_STORED_ITEM(mr).stored != None) XFreePixmap(dpy, MR_STORED_ITEM(mr).stored); MR_STORED_ITEM(mr).stored = None; MR_STORED_ITEM(mr).width = 0; MR_STORED_ITEM(mr).height = 0; MR_STORED_ITEM(mr).y = 0; } else if (select == False) { XEvent e; Picture *sidePic = NULL; e.type = Expose; e.xexpose.x = MST_BORDER_WIDTH(mr); e.xexpose.y = MI_Y_OFFSET(mi); e.xexpose.width = MR_WIDTH(mr) - 2 * MST_BORDER_WIDTH(mr); e.xexpose.height = MI_HEIGHT(mi) + (MI_IS_SELECTABLE(mi) ? MST_RELIEF_THICKNESS(mr) : 0);; if (MR_SIDEPIC(mr)) sidePic = MR_SIDEPIC(mr); else if (MST_SIDEPIC(mr)) sidePic = MST_SIDEPIC(mr); if (sidePic) { e.xexpose.width -= sidePic->width; if (MR_SIDEPIC_X_OFFSET(mr) == MST_BORDER_WIDTH(mr)) e.xexpose.x += sidePic->width; } MR_SELECTED_ITEM(mr) = (select) ? mi : NULL; paint_menu(mr, &e, fw); } break; default: if (MR_STORED_ITEM(mr).width != 0) { if (MR_STORED_ITEM(mr).stored != None) XFreePixmap(dpy, MR_STORED_ITEM(mr).stored); MR_STORED_ITEM(mr).stored = None; MR_STORED_ITEM(mr).width = 0; MR_STORED_ITEM(mr).height = 0; MR_STORED_ITEM(mr).y = 0; } break; } /* switch */ } /* if */ if (fw && !check_if_fvwm_window_exists(fw)) fw = NULL; MR_SELECTED_ITEM(mr) = (select) ? mi : NULL; paint_item(mr, mi, fw, False); } /*********************************************************************** * * Procedure: * pop_menu_down - unhighlight the current menu selection and * take down the menus * * mr - menu to pop down; this pointer is invalid after the function * returns. Don't use it anymore! * parent - the menu that has spawned mr (may be NULL). this is * used to see if mr was spawned by itself on some level. * this is a hack to allow specifying 'Popup foo' within * menu foo. You must use the MenuRoot that is currently * being processed here. DO NOT USE MR_PARENT_MENU(mr) here! * ***********************************************************************/ static void pop_menu_down(MenuRoot **pmr, MenuParameters *pmp) { MenuItem *mi; assert(*pmr); memset(&(MR_DYNAMIC_FLAGS(*pmr)), 0, sizeof(MR_DYNAMIC_FLAGS(*pmr))); XUnmapWindow(dpy, MR_WINDOW(*pmr)); MR_MAPPED_COPIES(*pmr)--; MST_USAGE_COUNT(*pmr)--; UninstallFvwmColormap(); XFlush(dpy); if (*(pmp->pcontext) & (C_WINDOW | C_FRAME | C_TITLE | C_SIDEBAR)) pmp->flags.is_menu_from_frame_or_window_or_titlebar = True; else pmp->flags.is_menu_from_frame_or_window_or_titlebar = False; if ((mi = MR_SELECTED_ITEM(*pmr)) != NULL) { select_menu_item(*pmr, mi, False, (*pmp->pTmp_win)); } if (MR_COPIES(*pmr) > 1) { /* delete this instance of the menu */ DestroyMenu(*pmr, False, False); } else if (MR_POPDOWN_ACTION(*pmr)) { /* Finally execute the popdown action (if defined). */ saved_pos_hints pos_hints; exec_func_args_type efa; Time t; extern XEvent Event; /* save variables that we still need but that may be overwritten */ pos_hints = last_saved_pos_hints; t = lastTimestamp; /* Execute the action */ memset(&efa, 0, sizeof(efa)); efa.eventp = &Event; efa.tmp_win = (*pmp->pTmp_win); efa.action = MR_POPDOWN_ACTION(*pmr); efa.context = *(pmp->pcontext); efa.module = -2; efa.flags.do_save_tmpwin = 1; efa.flags.exec = FUNC_DONT_EXPAND_COMMAND; execute_function(&efa); /* restore the stuff we saved */ last_saved_pos_hints = pos_hints; lastTimestamp = t; if (!check_if_fvwm_window_exists(*(pmp->pTmp_win))) { *(pmp->pTmp_win) = NULL; *(pmp->pcontext) = 0; } } return; } /*********************************************************************** * * Procedure: * pop_menu_down_and_repaint_parent - Pops down a menu and repaints the * overlapped portions of the parent menu. This is done only if * *fSubmenuOverlaps is True. *fSubmenuOverlaps is set to False * afterwards. * ***********************************************************************/ static void pop_menu_down_and_repaint_parent( MenuRoot **pmr, Bool *fSubmenuOverlaps, MenuParameters *pmp) { MenuRoot *parent = MR_PARENT_MENU(*pmr); Window win; XEvent event; int mr_x; int mr_y; unsigned int mr_width; unsigned int mr_height; int parent_x; int parent_y; unsigned int parent_width; unsigned int parent_height; if (*fSubmenuOverlaps && parent) { /* popping down the menu may destroy the menu via the dynamic popdown * action! Thus we must not access *pmr afterwards. */ win = MR_WINDOW(*pmr); /* Create a fake event to pass into paint_menu */ event.type = Expose; if (!XGetGeometry(dpy, win, &JunkRoot, &mr_x, &mr_y, &mr_width, &mr_height, &JunkBW, &JunkDepth) || !XGetGeometry(dpy, MR_WINDOW(parent), &JunkRoot, &parent_x, &parent_y, &parent_width, &parent_height, &JunkBW, &JunkDepth)) { pop_menu_down(pmr, pmp); paint_menu(parent, NULL, (*pmp->pTmp_win)); } else { pop_menu_down(pmr, pmp); event.xexpose.x = mr_x - parent_x; event.xexpose.width = mr_width; if (event.xexpose.x < 0) { event.xexpose.width += event.xexpose.x; event.xexpose.x = 0; } if (event.xexpose.x + event.xexpose.width > parent_width) { event.xexpose.width = parent_width - event.xexpose.x; } event.xexpose.y = mr_y - parent_y; event.xexpose.height = mr_height; if (event.xexpose.y < 0) { event.xexpose.height += event.xexpose.y; event.xexpose.y = 0; } if (event.xexpose.y + event.xexpose.height > parent_height) { event.xexpose.height = parent_height - event.xexpose.y; } flush_accumulate_expose(MR_WINDOW(parent), &event); paint_menu(parent, &event, (*pmp->pTmp_win)); } } else { /* popping down the menu may destroy the menu via the dynamic popdown * action! Thus we must not access *pmr afterwards. */ pop_menu_down(pmr, pmp); } *fSubmenuOverlaps = False; return; } /*********************************************************************** * * Procedure: * paint_item - draws a single entry in a popped up menu * * mr - the menu instance that holds the menu item * mi - the menu item to redraw * fw - the FvwmWindow structure to check against allowed functions * ***********************************************************************/ static void paint_item(MenuRoot *mr, MenuItem *mi, FvwmWindow *fw, Bool do_redraw_menu_border) { int y_offset; int text_y; int y_height; int x; int y; GC ShadowGC, ReliefGC, currentGC; short relief_thickness = MST_RELIEF_THICKNESS(mr); Bool is_item_selected; int i; int sx1; int sx2; if (!mi) return; is_item_selected = (mi == MR_SELECTED_ITEM(mr)); y_offset = MI_Y_OFFSET(mi); y_height = MI_HEIGHT(mi); if (MI_IS_SELECTABLE(mi)) { text_y = y_offset + MR_ITEM_TEXT_Y_OFFSET(mr); } else { text_y = y_offset + MST_PSTDFONT(mr)->y + MST_TITLE_GAP_ABOVE(mr); } /* center text vertically if the pixmap is taller */ if (MI_PICTURE(mi)) { text_y += MI_PICTURE(mi)->height; } for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++) { y = 0; if (MI_MINI_ICON(mi)[i]) { if (MI_MINI_ICON(mi)[i]->height > y) y = MI_MINI_ICON(mi)[i]->height; } y -= MST_PSTDFONT(mr)->height; if (y > 1) { text_y += y / 2; } } ShadowGC = MST_MENU_SHADOW_GC(mr); if(Pdepth<2) ReliefGC = MST_MENU_SHADOW_GC(mr); else ReliefGC = MST_MENU_RELIEF_GC(mr); /*************************************************************** * Hilight the item. ***************************************************************/ /* Hilight or clear the background. */ if (is_item_selected && MST_DO_HILIGHT(mr)) { /* Hilight the background. */ if (MR_HILIGHT_WIDTH(mr) - 2 * relief_thickness > 0) { XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv); XFillRectangle(dpy, MR_WINDOW(mr), MST_MENU_ACTIVE_BACK_GC(mr), MR_HILIGHT_X_OFFSET(mr) + relief_thickness, y_offset + relief_thickness, MR_HILIGHT_WIDTH(mr) - 2 * relief_thickness, y_height - relief_thickness); } } else if (MI_WAS_DESELECTED(mi) && (relief_thickness > 0 || MST_DO_HILIGHT(mr)) && (MST_FACE(mr).type != GradientMenu || MST_HAS_MENU_CSET(mr))) { int d = 0; if (MI_PREV_ITEM(mi) && MR_SELECTED_ITEM(mr) == MI_PREV_ITEM(mi)) { /* Don't paint over the hilight relief. */ d = relief_thickness; } /* Undo the hilighting. */ XClearArea(dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr), y_offset + d, MR_ITEM_WIDTH(mr), y_height + relief_thickness - d, 0); } MI_WAS_DESELECTED(mi) = False; /* Hilight 3D */ if (is_item_selected && relief_thickness > 0) { GC rgc = ReliefGC; GC sgc = ShadowGC; if (MST_HAS_ACTIVE_CSET(mr)) { rgc = MST_MENU_ACTIVE_RELIEF_GC(mr); sgc = MST_MENU_ACTIVE_SHADOW_GC(mr); } if (MST_IS_ITEM_RELIEF_REVERSED(mr)) { GC tgc = rgc; /* swap gcs for reversed relief */ rgc = sgc; sgc = tgc; } if (MR_HILIGHT_WIDTH(mr) - 2 * relief_thickness > 0) { /* The relief reaches down into the next item, hence the value for the * second y coordinate: MI_HEIGHT(mi) + 1 */ RelieveRectangle( dpy, MR_WINDOW(mr), MR_HILIGHT_X_OFFSET(mr), y_offset, MR_HILIGHT_WIDTH(mr) - 1, MI_HEIGHT(mi) - 1 + relief_thickness, rgc, sgc, relief_thickness); } } /*************************************************************** * Draw the item itself. ***************************************************************/ /* Calculate the separator offsets. */ if (MST_HAS_LONG_SEPARATORS(mr)) { sx1 = MR_ITEM_X_OFFSET(mr) + relief_thickness; sx2 = MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1 - relief_thickness; } else { sx1 = MR_ITEM_X_OFFSET(mr) + relief_thickness + MENU_SEPARATOR_SHORT_X_OFFSET; sx2 = MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) - 1 - relief_thickness - MENU_SEPARATOR_SHORT_X_OFFSET; } if (MI_IS_SEPARATOR(mi)) { if (sx1 < sx2) { /* It's a separator. */ draw_separator(MR_WINDOW(mr), ShadowGC, ReliefGC, sx1, y_offset + y_height - MENU_SEPARATOR_HEIGHT, sx2, y_offset + y_height - MENU_SEPARATOR_HEIGHT, 1); /* Nothing else to do. */ } return; } else if(MI_IS_TITLE(mi)) { /* Separate the title. */ if (MST_TITLE_UNDERLINES(mr) > 0 && mi != MR_FIRST_ITEM(mr)) { int add = (MI_IS_SELECTABLE(MI_PREV_ITEM(mi))) ? relief_thickness : 0; text_y += MENU_SEPARATOR_HEIGHT + add; y = y_offset + add; if (sx1 < sx2) draw_separator(MR_WINDOW(mr), ShadowGC, ReliefGC, sx1, y, sx2, y, 1); } /* Underline the title. */ switch (MST_TITLE_UNDERLINES(mr)) { case 0: break; case 1: if(MI_NEXT_ITEM(mi) != NULL) { y = y_offset + y_height - MENU_SEPARATOR_HEIGHT; draw_separator(MR_WINDOW(mr), ShadowGC, ReliefGC, sx1, y, sx2, y, 1); } break; default: for (i = MST_TITLE_UNDERLINES(mr); i-- > 0; ) { y = y_offset + y_height - 1 - i * MENU_UNDERLINE_HEIGHT; XDrawLine(dpy, MR_WINDOW(mr), ShadowGC, sx1, y, sx2, y); } break; } } /* Note: it's ok to pass a NULL label to check_if_function_allowed. */ if(check_if_function_allowed(MI_FUNC_TYPE(mi), fw, False, MI_LABEL(mi)[0])) { currentGC = (is_item_selected) ? MST_MENU_ACTIVE_GC(mr) : MST_MENU_GC(mr); if (MST_DO_HILIGHT(mr) && !MST_HAS_ACTIVE_FORE(mr) && !MST_HAS_ACTIVE_CSET(mr) && is_item_selected) { /* Use a lighter color for highlighted windows menu items if the * background is hilighted */ currentGC = MST_MENU_RELIEF_GC(mr); } } else { /* should be a shaded out word, not just re-colored. */ currentGC = MST_MENU_STIPPLE_GC(mr); } /*************************************************************** * Draw the labels. ***************************************************************/ for (i = MAX_MENU_ITEM_LABELS; i-- > 0; ) { if (MI_LABEL(mi)[i] && *(MI_LABEL(mi)[i])) { #ifdef I18N_MB XmbDrawString(dpy, MR_WINDOW(mr), MST_PSTDFONT(mr)->fontset, currentGC, MI_LABEL_OFFSET(mi)[i], text_y, MI_LABEL(mi)[i], MI_LABEL_STRLEN(mi)[i]); #else XDrawString(dpy, MR_WINDOW(mr), currentGC, MI_LABEL_OFFSET(mi)[i], text_y, MI_LABEL(mi)[i], MI_LABEL_STRLEN(mi)[i]); #endif } if (MI_HAS_HOTKEY(mi) && !MI_IS_TITLE(mi) && (!MI_IS_HOTKEY_AUTOMATIC(mi) || MST_USE_AUTOMATIC_HOTKEYS(mr)) && MI_HOTKEY_COLUMN(mi) == i) { /* pete@tecc.co.uk: If the item has a hot key, underline it */ draw_underline(mr, currentGC, MI_LABEL_OFFSET(mi)[i], text_y, MI_LABEL(mi)[i], MI_HOTKEY_COFFSET(mi)); } } /*************************************************************** * Draw the submenu triangle. ***************************************************************/ if(MI_IS_POPUP(mi)) { y = y_offset + (y_height - MENU_TRIANGLE_HEIGHT + relief_thickness) / 2; DrawTrianglePattern( dpy, MR_WINDOW(mr), ReliefGC, ShadowGC, (is_item_selected) ? ReliefGC : MST_MENU_GC(mr), MR_TRIANGLE_X_OFFSET(mr), y, MENU_TRIANGLE_WIDTH, MENU_TRIANGLE_HEIGHT, 0, (MR_IS_LEFT_TRIANGLE(mr)) ? 'l' : 'r', MST_HAS_TRIANGLE_RELIEF(mr), !MST_HAS_TRIANGLE_RELIEF(mr), is_item_selected); } /*************************************************************** * Draw the item picture. ***************************************************************/ if(MI_PICTURE(mi)) { x = menu_middle_x_offset(mr) - MI_PICTURE(mi)->width / 2; y = y_offset + ((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0); if(MI_PICTURE(mi)->depth == Pdepth) /* pixmap */ { Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin; Globalgcv.clip_mask = MI_PICTURE(mi)->mask; Globalgcv.clip_x_origin = x; Globalgcv.clip_y_origin = y; XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv); XCopyArea(dpy, MI_PICTURE(mi)->picture, MR_WINDOW(mr), ReliefGC, 0, 0, MI_PICTURE(mi)->width, MI_PICTURE(mi)->height, x, y); Globalgcm = GCClipMask; Globalgcv.clip_mask = None; XChangeGC(dpy, ReliefGC, Globalgcm, &Globalgcv); } else { XCopyPlane(dpy, MI_PICTURE(mi)->picture, MR_WINDOW(mr), currentGC, 0, 0, MI_PICTURE(mi)->width, MI_PICTURE(mi)->height, x, y, 1); } } /*************************************************************** * Draw the mini icons. ***************************************************************/ for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++) { int k; /* We need to reverse the mini icon order for left submenu style. */ k = (MST_USE_LEFT_SUBMENUS(mr)) ? MAX_MENU_ITEM_MINI_ICONS - 1 - i : i; if (MI_MINI_ICON(mi)[i]) { if (MI_PICTURE(mi)) { y = y_offset + MI_HEIGHT(mi) - MI_MINI_ICON(mi)[i]->height; } else { y = y_offset + (MI_HEIGHT(mi) + ((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0) - MI_MINI_ICON(mi)[i]->height) / 2; } Globalgcm = GCClipMask | GCClipXOrigin | GCClipYOrigin; Globalgcv.clip_x_origin = MR_ICON_X_OFFSET(mr)[k]; Globalgcv.clip_y_origin = y; if(MI_MINI_ICON(mi)[i]->depth == Pdepth) { /* pixmap */ Globalgcv.clip_mask = MI_MINI_ICON(mi)[i]->mask; XChangeGC(dpy,currentGC,Globalgcm,&Globalgcv); XCopyArea( dpy, MI_MINI_ICON(mi)[i]->picture, MR_WINDOW(mr), currentGC, 0, 0, MI_MINI_ICON(mi)[i]->width, MI_MINI_ICON(mi)[i]->height, MR_ICON_X_OFFSET(mr)[k], y); } else { /* monochrome bitmap */ Globalgcv.clip_mask = MI_MINI_ICON(mi)[i]->picture; XChangeGC(dpy,currentGC,Globalgcm,&Globalgcv); XCopyPlane( dpy, MI_MINI_ICON(mi)[i]->picture, MR_WINDOW(mr), currentGC, 0, 0, MI_MINI_ICON(mi)[i]->width, MI_MINI_ICON(mi)[i]->height, MR_ICON_X_OFFSET(mr)[k], y, 1); } Globalgcm = GCClipMask; Globalgcv.clip_mask = None; XChangeGC(dpy, currentGC, Globalgcm, &Globalgcv); } } return; } /************************************************************ * * Draws a picture on the left side of the menu * ************************************************************/ static void paint_side_pic(MenuRoot *mr, XEvent *pevent) { GC ReliefGC, TextGC; Picture *sidePic; int ys; int yt; int h; int bw = MST_BORDER_WIDTH(mr); if (MR_SIDEPIC(mr)) sidePic = MR_SIDEPIC(mr); else if (MST_SIDEPIC(mr)) sidePic = MST_SIDEPIC(mr); else return; if(Pdepth < 2) { ReliefGC = MST_MENU_SHADOW_GC(mr); } else { ReliefGC = MST_MENU_RELIEF_GC(mr); } TextGC = MST_MENU_GC(mr); if(MR_HAS_SIDECOLOR(mr)) { Globalgcv.foreground = MR_SIDECOLOR(mr); } else if (MST_HAS_SIDE_COLOR(mr)) { Globalgcv.foreground = MST_SIDE_COLOR(mr); } if (MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr)) { Globalgcm = GCForeground; XChangeGC(dpy, Scr.ScratchGC1, Globalgcm, &Globalgcv); XFillRectangle(dpy, MR_WINDOW(mr), Scr.ScratchGC1, MR_SIDEPIC_X_OFFSET(mr), bw, sidePic->width, MR_HEIGHT(mr) - 2 * bw); } if (sidePic->height > MR_HEIGHT(mr) - 2 * bw) { h = MR_HEIGHT(mr) - 2 * bw; ys = sidePic->height - h; yt = bw; } else { h = sidePic->height; ys = 0; yt = MR_HEIGHT(mr) - bw - sidePic->height; } if(sidePic->depth == Pdepth) /* pixmap */ { Globalgcv.clip_mask = sidePic->mask; Globalgcv.clip_x_origin = MR_SIDEPIC_X_OFFSET(mr); Globalgcv.clip_y_origin = yt - ys; XChangeGC(dpy, ReliefGC, GCClipMask | GCClipXOrigin | GCClipYOrigin, &Globalgcv); XCopyArea(dpy, sidePic->picture, MR_WINDOW(mr), ReliefGC, 0, ys, sidePic->width, h, MR_SIDEPIC_X_OFFSET(mr), yt); Globalgcv.clip_mask = None; XChangeGC(dpy, ReliefGC, GCClipMask, &Globalgcv); } else { XCopyPlane(dpy, sidePic->picture, MR_WINDOW(mr), TextGC, 0, ys, sidePic->width, h, MR_SIDEPIC_X_OFFSET(mr), yt, 1); } } /**************************************************************************** * Procedure: * draw_underline() - Underline a character in a string (pete@tecc.co.uk) * * Calculate the pixel offsets to the start of the character position we * want to underline and to the next character in the string. Shrink by * one pixel from each end and the draw a line that long two pixels below * the character... * ****************************************************************************/ static void draw_underline(MenuRoot *mr, GC gc, int x, int y, char *txt, int coffset) { int off1 = XTextWidth(MST_PSTDFONT(mr)->font, txt, coffset); int off2 = XTextWidth(MST_PSTDFONT(mr)->font, txt + coffset, 1) - 1 + off1; XDrawLine(dpy, MR_WINDOW(mr), gc, x + off1, y + 2, x + off2, y + 2); } /**************************************************************************** * * Draws two horizontal lines to form a separator * ****************************************************************************/ static void draw_separator(Window w, GC TopGC, GC BottomGC, int x1, int y1, int x2, int y2, int extra_off) { XDrawLine(dpy, w, TopGC , x1, y1, x2, y2); XDrawLine(dpy, w, BottomGC, x1-extra_off, y1+1,x2+extra_off,y2+1); } /*********************************************************************** * * Procedure: * paint_menu - draws the entire menu * ***********************************************************************/ static void paint_menu(MenuRoot *mr, XEvent *pevent, FvwmWindow *fw) { MenuItem *mi; MenuStyle *ms = MR_STYLE(mr); XRectangle bounds; int bw = MST_BORDER_WIDTH(mr); Picture *p; int width, height, x, y; Pixmap pmap; GC pmapgc; XGCValues gcv; unsigned long gcm = GCLineWidth; Bool do_clear = False; gcv.line_width = 3; if (fw && !check_if_fvwm_window_exists(fw)) fw = NULL; if (MR_IS_PAINTED(mr) && pevent && (pevent->xexpose.x >= MR_WIDTH(mr) - bw || pevent->xexpose.x + pevent->xexpose.width <= bw || pevent->xexpose.y >= MR_HEIGHT(mr) - bw || pevent->xexpose.y + pevent->xexpose.height <= bw)) { /* Only the border was obscured. Redraw it centrally instead of redrawing * several menu items. */ RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1, MR_HEIGHT(mr) - 1, (Pdepth < 2) ? MST_MENU_SHADOW_GC(mr) : MST_MENU_RELIEF_GC(mr), MST_MENU_SHADOW_GC(mr), bw); return; } MR_IS_PAINTED(mr) = 1; if (ms && ST_HAS_MENU_CSET(ms)) { if (MR_IS_BACKGROUND_SET(mr) == False) { SetWindowBackground( dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr), &Colorset[ST_CSET_MENU(ms)], Pdepth, ST_MENU_GC(ms), True); MR_IS_BACKGROUND_SET(mr) = True; } } else if (ms) { register int type; type = ST_FACE(ms).type; switch(type) { case SolidMenu: XSetWindowBackground(dpy, MR_WINDOW(mr), MST_FACE(mr).u.back); flush_expose(MR_WINDOW(mr)); do_clear = True; break; case GradientMenu: bounds.x = bw; bounds.y = bw; bounds.width = MR_WIDTH(mr) - bw; bounds.height = MR_HEIGHT(mr) - bw; /* H, V, D and B gradients are optimized and have their own code here. */ switch (ST_FACE(ms).gradient_type) { case H_GRADIENT: if (MR_IS_BACKGROUND_SET(mr) == False) { register int i; register int dw; pmap = XCreatePixmap(dpy, MR_WINDOW(mr), MR_WIDTH(mr), DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS, Pdepth); pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv); dw = (float) (bounds.width / ST_FACE(ms).u.grad.npixels) + 1; for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++) { unsigned short x = i * bounds.width/ST_FACE(ms).u.grad.npixels; XSetForeground(dpy, pmapgc, ST_FACE(ms).u.grad.pixels[i]); XFillRectangle(dpy, pmap, pmapgc, x, 0, dw, DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS); } XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap); XFreeGC(dpy,pmapgc); XFreePixmap(dpy,pmap); MR_IS_BACKGROUND_SET(mr) = True; } do_clear = True; break; case V_GRADIENT: if (MR_IS_BACKGROUND_SET(mr) == False) { register int i; register int dh; static unsigned int best_tile_width = 0; unsigned int junk; if (best_tile_width == 0) { if (!XQueryBestTile( dpy, Scr.screen, DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS, DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS, &best_tile_width, &junk)) { /* call failed, use default and risk a screwed up tile */ best_tile_width = DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS; } } pmap = XCreatePixmap(dpy, MR_WINDOW(mr), best_tile_width, MR_HEIGHT(mr), Pdepth); pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv); dh = (float) (bounds.height / ST_FACE(ms).u.grad.npixels) + 1; for (i = 0; i < ST_FACE(ms).u.grad.npixels; i++) { unsigned short y = i * bounds.height/ST_FACE(ms).u.grad.npixels; XSetForeground(dpy, pmapgc, ST_FACE(ms).u.grad.pixels[i]); XFillRectangle(dpy, pmap, pmapgc, 0, y, best_tile_width, dh); } XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap); XFreeGC(dpy,pmapgc); XFreePixmap(dpy,pmap); MR_IS_BACKGROUND_SET(mr) = True; } do_clear = True; break; case D_GRADIENT: case B_GRADIENT: { register int i = 0, numLines; int cindex = -1; XRectangle r; Picture *sidePic = NULL; if (MR_SIDEPIC(mr)) sidePic = MR_SIDEPIC(mr); else if (MST_SIDEPIC(mr)) sidePic = MST_SIDEPIC(mr); if (pevent) { r.x = pevent->xexpose.x; r.y = pevent->xexpose.y; r.width = pevent->xexpose.width; r.height = pevent->xexpose.height; } else { r.x = bw; r.y = bw; r.width = MR_WIDTH(mr) - 2 * bw; r.height = MR_HEIGHT(mr) - 2 * bw; } XSetClipRectangles(dpy, Scr.TransMaskGC, 0, 0, &r, 1, Unsorted); numLines = MR_WIDTH(mr) + MR_HEIGHT(mr) - 2 * bw; for(i = 0; i < numLines; i++) { if((int)(i * ST_FACE(ms).u.grad.npixels / numLines) > cindex) { /* pick the next colour (skip if necc.) */ cindex = i * ST_FACE(ms).u.grad.npixels / numLines; XSetForeground(dpy, Scr.TransMaskGC, ST_FACE(ms).u.grad.pixels[cindex]); } if (ST_FACE(ms).gradient_type == D_GRADIENT) XDrawLine(dpy, MR_WINDOW(mr), Scr.TransMaskGC, 0, i, i, 0); else /* B_GRADIENT */ XDrawLine(dpy, MR_WINDOW(mr), Scr.TransMaskGC, 0, MR_HEIGHT(mr) - 1 - i, i, MR_HEIGHT(mr) - 1); } } XSetClipMask(dpy, Scr.TransMaskGC, None); break; default: if (MR_IS_BACKGROUND_SET(mr) == False) { unsigned int g_width; unsigned int g_height; /* let library take care of all other gradients */ pmap = XCreatePixmap(dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr), Pdepth); pmapgc = fvwmlib_XCreateGC(dpy, pmap, gcm, &gcv); /* find out the size the pixmap should be */ CalculateGradientDimensions( dpy, MR_WINDOW(mr), ST_FACE(ms).u.grad.npixels, ST_FACE(ms).gradient_type, &g_width, &g_height); /* draw the gradient directly into the window */ CreateGradientPixmap( dpy, MR_WINDOW(mr), pmapgc, ST_FACE(ms).gradient_type, g_width, g_height, ST_FACE(ms).u.grad.npixels, ST_FACE(ms).u.grad.pixels, pmap, bw, bw, MR_WIDTH(mr) - bw, MR_HEIGHT(mr) - bw, NULL); XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), pmap); XFreeGC(dpy, pmapgc); XFreePixmap(dpy, pmap); MR_IS_BACKGROUND_SET(mr) = True; } do_clear = True; break; } break; case PixmapMenu: p = ST_FACE(ms).u.p; width = MR_WIDTH(mr) - 2 * bw; height = MR_HEIGHT(mr) - 2 * bw; #if 0 /* these flags are never set at the moment */ x = border; if (ST_FACE(ms)Style & HOffCenter) { if (ST_FACE(ms)Style & HRight) x += (int)(width - p->width); } else x += (int)(width - p->width) / 2; y = border; if (ST_FACE(ms)Style & VOffCenter) { if (ST_FACE(ms)Style & VBottom) y += (int)(height - p->height); } else... #else y = (int)(height - p->height) / 2; x = (int)(width - p->width) / 2; #endif if (x < bw) x = bw; if (y < bw) y = bw; if (width > p->width) width = p->width; if (height > p->height) height = p->height; if (width > MR_WIDTH(mr) - x - bw) width = MR_WIDTH(mr) - x - bw; if (height > MR_HEIGHT(mr) - y - bw) height = MR_HEIGHT(mr) - y - bw; XSetClipMask(dpy, Scr.TransMaskGC, p->mask); XSetClipOrigin(dpy, Scr.TransMaskGC, x, y); XCopyArea(dpy, p->picture, MR_WINDOW(mr), Scr.TransMaskGC, bw, bw, width, height, x, y); break; case TiledPixmapMenu: XSetWindowBackgroundPixmap(dpy, MR_WINDOW(mr), ST_FACE(ms).u.p->picture); flush_expose(MR_WINDOW(mr)); do_clear = True; break; } /* switch(type) */ } /* if (ms) */ if (do_clear == True) { if (pevent == NULL) { XClearWindow(dpy, MR_WINDOW(mr)); } else { XClearArea( dpy, MR_WINDOW(mr), pevent->xexpose.x, pevent->xexpose.y, pevent->xexpose.width, pevent->xexpose.height, False); } } /* draw the relief */ RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1, MR_HEIGHT(mr) - 1, (Pdepth < 2) ? MST_MENU_SHADOW_GC(mr) : MST_MENU_RELIEF_GC(mr), MST_MENU_SHADOW_GC(mr), bw); for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi)) { /* be smart about handling the expose, redraw only the entries * that we need to */ if (pevent == NULL || (pevent->xexpose.y < (MI_Y_OFFSET(mi) + MI_HEIGHT(mi) + ST_RELIEF_THICKNESS(MR_STYLE(mr))) && (pevent->xexpose.y + pevent->xexpose.height) > MI_Y_OFFSET(mi))) { paint_item(mr, mi, fw, True); } } paint_side_pic(mr, pevent); XSync(dpy, 0); return; } static void FreeMenuItem(MenuItem *mi) { short i; if (!mi) return; for (i = 0; i < MAX_MENU_ITEM_LABELS; i++) { if (MI_LABEL(mi)[i] != NULL) free(MI_LABEL(mi)[i]); } if (MI_ACTION(mi) != NULL) free(MI_ACTION(mi)); if(MI_PICTURE(mi)) DestroyPicture(dpy,MI_PICTURE(mi)); for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++) { if(MI_MINI_ICON(mi)[i]) DestroyPicture(dpy, MI_MINI_ICON(mi)[i]); } free(mi); } Bool DestroyMenu(MenuRoot *mr, Bool do_recreate, Bool is_command_request) { MenuItem *mi,*tmp2; MenuRoot *tmp, *prev; if (mr == NULL) return False; /* seek menu in master list */ tmp = Menus.all; prev = NULL; while (tmp && tmp != mr) { prev = tmp; tmp = MR_NEXT_MENU(tmp); } if (tmp != mr) { /* no such menu */ return False; } if (MR_MAPPED_COPIES(mr) > 0 && (is_command_request || MR_COPIES(mr) == 1)) { /* can't destroy a menu while in use */ fvwm_msg(ERR,"DestroyMenu", "Menu %s is in use", MR_NAME(mr)); return False; } if (MR_COPIES(mr) > 1 && MR_ORIGINAL_MENU(mr) == mr) { MenuRoot *m; MenuRoot *new_orig; /* find a new 'original' menu */ for (m = Menus.all, new_orig = NULL; m; m = MR_NEXT_MENU(m)) { if (m != mr && MR_ORIGINAL_MENU(m) == mr) { if (new_orig == NULL) { new_orig = m; } MR_ORIGINAL_MENU(m) = new_orig; } } MR_ORIGINAL_MENU(mr) = new_orig; /* now dump old original menu */ } MR_COPIES(mr)--; if (MR_STORED_ITEM(mr).stored) XFreePixmap(dpy, MR_STORED_ITEM(mr).stored); if (MR_COPIES(mr) > 0) { do_recreate = False; } else { /* free all items */ mi = MR_FIRST_ITEM(mr); while (mi != NULL) { tmp2 = MI_NEXT_ITEM(mi); FreeMenuItem(mi); mi = tmp2; } if (do_recreate) { /* just dump the menu items but keep the menu itself */ MR_COPIES(mr)++; MR_FIRST_ITEM(mr) = NULL; MR_LAST_ITEM(mr) = NULL; MR_SELECTED_ITEM(mr) = NULL; MR_CONTINUATION_MENU(mr) = NULL; MR_PARENT_MENU(mr) = NULL; MR_ITEMS(mr) = 0; memset(&(MR_STORED_ITEM(mr)), 0 , sizeof(MR_STORED_ITEM(mr))); MR_IS_UPDATED(mr) = 1; return True; } } /* unlink menu from list */ if (prev == NULL) Menus.all = MR_NEXT_MENU(mr); else MR_NEXT_MENU(prev) = MR_NEXT_MENU(mr); XDestroyWindow(dpy,MR_WINDOW(mr)); XDeleteContext(dpy, MR_WINDOW(mr), MenuContext); if (MR_COPIES(mr) == 0) { if (MR_POPUP_ACTION(mr)) free(MR_POPUP_ACTION(mr)); if (MR_POPDOWN_ACTION(mr)) free(MR_POPDOWN_ACTION(mr)); if (MR_MISSING_SUBMENU_FUNC(mr)) free(MR_MISSING_SUBMENU_FUNC(mr)); free(MR_NAME(mr)); if (MR_SIDEPIC(mr)) DestroyPicture(dpy, MR_SIDEPIC(mr)); free(mr->s); } free(mr->d); free(mr); return True; } void CMD_DestroyMenu(F_CMD_ARGS) { MenuRoot *mr; MenuRoot *mrContinuation; Bool do_recreate = False; char *token; token = PeekToken(action, &action); if (!token) return; if (StrEquals(token, "recreate")) { do_recreate = True; token = PeekToken(action, NULL); } mr = FindPopup(token); if (Scr.last_added_item.type == ADDED_MENU) set_last_added_item(ADDED_NONE, NULL); while (mr) { /* save continuation before destroy */ mrContinuation = MR_CONTINUATION_MENU(mr); if (!DestroyMenu(mr, do_recreate, True)) { return; } /* Don't recreate the continuations */ do_recreate = False; mr = mrContinuation; } return; } /**************************************************************************** * * Merge menu continuations back into the original menu. * Called by make_menu(). * ****************************************************************************/ static void merge_continuation_menus(MenuRoot *mr) { /* merge menu continuations into one menu again - needed when changing the * font size of a long menu. */ while (MR_CONTINUATION_MENU(mr) != NULL) { MenuRoot *cont = MR_CONTINUATION_MENU(mr); /* link first item of continuation to item before 'more...' */ MI_NEXT_ITEM(MI_PREV_ITEM(MR_LAST_ITEM(mr))) = MR_FIRST_ITEM(cont); MI_PREV_ITEM(MR_FIRST_ITEM(cont)) = MI_PREV_ITEM(MR_LAST_ITEM(mr)); FreeMenuItem(MR_LAST_ITEM(mr)); MR_LAST_ITEM(mr) = MR_LAST_ITEM(cont); MR_CONTINUATION_MENU(mr) = MR_CONTINUATION_MENU(cont); /* fake an empty menu so that DestroyMenu does not destroy the items. */ MR_FIRST_ITEM(cont) = NULL; DestroyMenu(cont, False, False); } return; } /**************************************************************************** * * Extract interesting values from the item format string that are needed by * the size_menu_... functions. * ****************************************************************************/ static void calculate_item_sizes(MenuSizingParameters *msp) { MenuItem *mi; unsigned short w; int i; int j; memset(&(msp->max), 0, sizeof(msp->max)); /* Calculate the widths for all columns of all items. */ for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi)) { if (MI_IS_POPUP(mi)) { msp->max.triangle_width = MENU_TRIANGLE_WIDTH; } else if (MI_IS_TITLE(mi) && !MI_HAS_PICTURE(mi)) { Bool is_formatted = False; /* titles stretch over the whole menu width, so count the maximum * separately if the title is unformatted. */ for (j = 1; j < MAX_MENU_ITEM_LABELS; j++) { if (MI_LABEL(mi)[j] != NULL) { is_formatted = True; break; } else { MI_LABEL_OFFSET(mi)[j] = 0; } } if (!is_formatted && MI_LABEL(mi)[0] != NULL) { MI_LABEL_STRLEN(mi)[0] = strlen(MI_LABEL(mi)[0]); w = XTextWidth(MST_PSTDFONT(msp->menu)->font, MI_LABEL(mi)[0], MI_LABEL_STRLEN(mi)[0]); MI_LABEL_OFFSET(mi)[0] = w; MI_IS_TITLE_CENTERED(mi) = True; if (msp->max.title_width < w) msp->max.title_width = w; continue; } } for (i = 0; i < MAX_MENU_ITEM_LABELS; i++) { if (MI_LABEL(mi)[i]) { MI_LABEL_STRLEN(mi)[i] = strlen(MI_LABEL(mi)[i]); w = XTextWidth(MST_PSTDFONT(msp->menu)->font, MI_LABEL(mi)[i], MI_LABEL_STRLEN(mi)[i]); MI_LABEL_OFFSET(mi)[i] = w; if (msp->max.label_width[i] < w) msp->max.label_width[i] = w; } } if (MI_PICTURE(mi) && msp->max.picture_width < MI_PICTURE(mi)->width) { msp->max.picture_width = MI_PICTURE(mi)->width; } for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++) { int k; /* We need to reverse the mini icon order for left submenu style. */ k = (MST_USE_LEFT_SUBMENUS(msp->menu)) ? MAX_MENU_ITEM_MINI_ICONS - 1 - i : i; if (MI_MINI_ICON(mi)[i] && msp->max.icon_width[k] < MI_MINI_ICON(mi)[i]->width) { msp->max.icon_width[k] = MI_MINI_ICON(mi)[i]->width; } } } /* for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi)) */ if (MR_SIDEPIC(msp->menu)) { msp->max.sidepic_width = MR_SIDEPIC(msp->menu)->width; } else if (MST_SIDEPIC(msp->menu)) { msp->max.sidepic_width = MST_SIDEPIC(msp->menu)->width; } return; } /**************************************************************************** * * Calculate the positions of the columns in the menu. * Called by make_menu(). * ****************************************************************************/ static void size_menu_horizontally(MenuSizingParameters *msp) { MenuItem *mi; Bool sidepic_is_left = True; int total_width; int sidepic_space = 0; unsigned short label_offset[MAX_MENU_ITEM_LABELS]; char lcr_column[MAX_MENU_ITEM_LABELS]; int i; int d; short relief_thickness = MST_RELIEF_THICKNESS(msp->menu); unsigned short *item_order[MAX_MENU_ITEM_LABELS + MAX_MENU_ITEM_MINI_ICONS + 1 /* triangle */ + 2 /* relief markers */]; short used_objects = 0; short left_objects = 0; short right_objects = 0; memset(item_order, 0, sizeof(item_order)); for (i = 0; i < MAX_MENU_ITEM_LABELS; i++) lcr_column[i] = 'l'; /* Now calculate the offsets for the columns. */ { int x; unsigned char icons_placed = 0; Bool sidepic_placed = False; Bool triangle_placed = False; Bool relief_begin_placed = False; Bool relief_end_placed = False; char *format; Bool first = True; Bool done = False; Bool is_last_object_left = True; unsigned char columns_placed = 0; int relief_gap = 0; int gap_left; int gap_right; int chars; format = MST_ITEM_FORMAT(msp->menu); if (!format) { format = (MST_USE_LEFT_SUBMENUS(msp->menu)) ? DEFAULT_LEFT_MENU_ITEM_FORMAT : DEFAULT_MENU_ITEM_FORMAT; } /* Place the individual items off the menu in case they are not set in the * format string. */ for (i = 0; i < MAX_MENU_ITEM_LABELS; i++) { label_offset[i] = 2 * Scr.MyDisplayWidth; } x = MST_BORDER_WIDTH(msp->menu); while (*format && !done) { switch (*format) { case '%': format++; chars = 0; gap_left = 0; gap_right = 0; /* Insert a gap of %d pixels. */ if (sscanf(format, "%d.%d%n", &gap_left, &gap_right, &chars) >= 2 || (sscanf(format, "%d.%n", &gap_left, &chars) >= 1 && chars > 0) || sscanf(format, "%d%n", &gap_left, &chars) >= 1 || sscanf(format, ".%d%n", &gap_right, &chars) >= 1) { if (gap_left > MR_SCREEN_WIDTH(msp->menu) || gap_left < -MR_SCREEN_WIDTH(msp->menu)) { gap_left = 0; } if (gap_right > MR_SCREEN_HEIGHT(msp->menu) || gap_right < -MR_SCREEN_HEIGHT(msp->menu)) { gap_right = 0; } /* Skip the number. */ format += chars; } else if (*format == '.') { /* Skip a dot without values */ format++; } if (!*format) break; switch (*format) { case 'l': case 'c': case 'r': /* A left, center or right aligned column. */ if (columns_placed < MAX_MENU_ITEM_LABELS) { if (msp->max.label_width[columns_placed] > 0) { lcr_column[columns_placed] = *format; x += gap_left; label_offset[columns_placed] = x; x += msp->max.label_width[columns_placed] + gap_right; item_order[used_objects++] = &(label_offset[columns_placed]); if (is_last_object_left && (*format == 'l')) left_objects++; else { is_last_object_left = False; if (*format == 'r') right_objects++; else right_objects = 0; } } columns_placed++; } break; case 's': /* the sidepic */ if (!sidepic_placed) { sidepic_placed = True; if (msp->max.sidepic_width > 0) { x += gap_left; if (!first) { /* Stop parsing immediately. %s must be first or last in the * format string. */ sidepic_is_left = False; done = True; } MR_SIDEPIC_X_OFFSET(msp->menu) = x; if (first) { sidepic_is_left = True; } sidepic_space = msp->max.sidepic_width + ((sidepic_is_left) ? gap_left : gap_right); x += msp->max.sidepic_width + gap_right; } } break; case 'i': /* a mini icon */ if (icons_placed < MAX_MENU_ITEM_MINI_ICONS) { if (msp->max.icon_width[icons_placed] > 0) { x += gap_left; MR_ICON_X_OFFSET(msp->menu)[icons_placed] = x; x += msp->max.icon_width[icons_placed] + gap_right; item_order[used_objects++] = &(MR_ICON_X_OFFSET(msp->menu)[icons_placed]); if (is_last_object_left) left_objects++; else right_objects++; } icons_placed++; } break; case '|': if (!relief_begin_placed) { relief_begin_placed = True; x += gap_left; MR_HILIGHT_X_OFFSET(msp->menu) = x; x += relief_thickness + gap_right; relief_gap += gap_right; item_order[used_objects++] = &(MR_HILIGHT_X_OFFSET(msp->menu)); if (is_last_object_left) left_objects++; else right_objects = 0; } else if (!relief_end_placed) { relief_end_placed = True; x += relief_thickness + gap_left; /* This is a hack: for now we record the x coordinate of the * end of the hilight area, but later we'll place the width in * here. */ MR_HILIGHT_WIDTH(msp->menu) = x; x += gap_right; relief_gap += gap_left; item_order[used_objects++] = &(MR_HILIGHT_WIDTH(msp->menu)); right_objects++; } break; case '>': case '<': /* the triangle for popup menus */ if (!triangle_placed) { triangle_placed = True; if (msp->max.triangle_width > 0) { x += gap_left; MR_TRIANGLE_X_OFFSET(msp->menu) = x; MR_IS_LEFT_TRIANGLE(msp->menu) = (*format == '<'); x += msp->max.triangle_width + gap_right; item_order[used_objects++] = &(MR_TRIANGLE_X_OFFSET(msp->menu)); if (is_last_object_left && *format == '<') left_objects++; else { is_last_object_left = False; right_objects++; } } } break; case 'p': /* Simply add a gap. */ x += gap_right + gap_left; break; case '\t': x += MENU_TAB_WIDTH * XTextWidth(MST_PSTDFONT(msp->menu)->font, " ", 1); break; case ' ': /* Advance the x position. */ x += XTextWidth(MST_PSTDFONT(msp->menu)->font, format, 1); break; default: /* Ignore unknown characters. */ break; } /* switch (*format) */ break; case '\t': x += MENU_TAB_WIDTH * XTextWidth(MST_PSTDFONT(msp->menu)->font, " ", 1); break; case ' ': /* Advance the x position. */ x += XTextWidth(MST_PSTDFONT(msp->menu)->font, format, 1); break; default: /* Ignore unknown characters. */ break; } /* switch (*format) */ format++; first = False; } /* while (*format) */ /* stored for vertical sizing */ msp->used_item_labels = columns_placed; msp->used_mini_icons = icons_placed; /* Hide unplaced parts of the menu. */ if (!sidepic_placed) { MR_SIDEPIC_X_OFFSET(msp->menu) = 2 * MR_SCREEN_WIDTH(msp->menu); } for (i = icons_placed; i < MAX_MENU_ITEM_MINI_ICONS; i++) { MR_ICON_X_OFFSET(msp->menu)[i] = 2 * MR_SCREEN_WIDTH(msp->menu); } if (!triangle_placed) { MR_TRIANGLE_X_OFFSET(msp->menu) = 2 * MR_SCREEN_WIDTH(msp->menu); } msp->flags.is_popup_indicator_used = triangle_placed; total_width = x - MST_BORDER_WIDTH(msp->menu); d = (sidepic_space + 2 * relief_thickness + max(msp->max.title_width, msp->max.picture_width)) - total_width; if (d > 0) { int m = 1 - left_objects; int n = 1 + used_objects - left_objects - right_objects; /* The title is larger than all menu items. Stretch the gaps between the * items up to the total width of the title. */ for (i = 0; i < used_objects; i++) { if (i >= left_objects) { if (i >= used_objects - right_objects) { /* Right aligned item. */ *(item_order[i]) += d; } else { #if 0 /* Neither left nor right aligned item. */ *(item_order[i]) += d / 2; #else /* Neither left nor right aligned item. Divide the overhead gap * evenly between the items. */ *(item_order[i]) += d * (m + i) / n; #endif } } } total_width += d; if (!sidepic_is_left) { MR_SIDEPIC_X_OFFSET(msp->menu) += d; } } /* if (d > 0) */ MR_WIDTH(msp->menu) = total_width + 2 * MST_BORDER_WIDTH(msp->menu); MR_ITEM_WIDTH(msp->menu) = total_width - sidepic_space; MR_ITEM_X_OFFSET(msp->menu) = MST_BORDER_WIDTH(msp->menu); if (sidepic_is_left) { MR_ITEM_X_OFFSET(msp->menu) += sidepic_space; } if (!relief_begin_placed) { MR_HILIGHT_X_OFFSET(msp->menu) = MR_ITEM_X_OFFSET(msp->menu); } if (relief_end_placed) { MR_HILIGHT_WIDTH(msp->menu) = MR_HILIGHT_WIDTH(msp->menu) - MR_HILIGHT_X_OFFSET(msp->menu); } else { MR_HILIGHT_WIDTH(msp->menu) = MR_ITEM_WIDTH(msp->menu) + MR_ITEM_X_OFFSET(msp->menu) - MR_HILIGHT_X_OFFSET(msp->menu); } } /* Now calculate the offsets for the individual labels. */ for (mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi)) { for (i = 0; i < MAX_MENU_ITEM_LABELS; i++) { if (MI_LABEL(mi)[i]) { if (!MI_IS_TITLE(mi) || !MI_IS_TITLE_CENTERED(mi)) { switch (lcr_column[i]) { case 'l': MI_LABEL_OFFSET(mi)[i] = label_offset[i]; break; case 'c': MI_LABEL_OFFSET(mi)[i] = label_offset[i] + (msp->max.label_width[i] - MI_LABEL_OFFSET(mi)[i]) / 2; break; case 'r': MI_LABEL_OFFSET(mi)[i] = label_offset[i] + msp->max.label_width[i] - MI_LABEL_OFFSET(mi)[i]; break; } } else { /* This is a centered title item (indicated by negative width). */ MI_LABEL_OFFSET(mi)[i] = menu_middle_x_offset(msp->menu) - MI_LABEL_OFFSET(mi)[i] / 2; } } } /* for */ } /* for */ return; } /**************************************************************************** * * Calculate the positions of the columns in the menu. * Called by make_menu(). * ****************************************************************************/ static Bool size_menu_vertically(MenuSizingParameters *msp) { MenuItem *mi; int y; int cItems; short relief_thickness = MST_RELIEF_THICKNESS(msp->menu); short simple_entry_height; int i; Bool has_continuation_menu = False; MR_ITEM_TEXT_Y_OFFSET(msp->menu) = MST_PSTDFONT(msp->menu)->y + relief_thickness + MST_ITEM_GAP_ABOVE(msp->menu); simple_entry_height = MST_PSTDFONT(msp->menu)->height + MST_ITEM_GAP_ABOVE(msp->menu) + MST_ITEM_GAP_BELOW(msp->menu); /* mi_prev trails one behind mi, since we need to move that into a newly-made menu if we run out of space */ y = MST_BORDER_WIDTH(msp->menu); for (cItems = 0, mi = MR_FIRST_ITEM(msp->menu); mi != NULL; mi = MI_NEXT_ITEM(mi), cItems++) { Bool last_item_has_relief = (MI_PREV_ITEM(mi)) ? MI_IS_SELECTABLE(MI_PREV_ITEM(mi)) : False; Bool has_mini_icon = False; int separator_height; separator_height = (last_item_has_relief) ? MENU_SEPARATOR_HEIGHT + relief_thickness : MENU_SEPARATOR_TOTAL_HEIGHT; MI_Y_OFFSET(mi) = y; if (MI_IS_TITLE(mi) && MI_IS_TITLE_CENTERED(mi)) { MI_HEIGHT(mi) = MST_PSTDFONT(msp->menu)->height + MST_TITLE_GAP_ABOVE(msp->menu) + MST_TITLE_GAP_BELOW(msp->menu); } else if (MI_IS_SEPARATOR(mi)) { /* Separator */ MI_HEIGHT(mi) = separator_height; } else { /* Normal text entry or an entry with a sub menu triangle */ if ((MI_HAS_TEXT(mi) && msp->used_item_labels) || (MI_IS_POPUP(mi) && msp->flags.is_popup_indicator_used)) { MI_HEIGHT(mi) = simple_entry_height + relief_thickness; } else { MI_HEIGHT(mi) = MST_ITEM_GAP_ABOVE(msp->menu) + MST_ITEM_GAP_BELOW(msp->menu) + relief_thickness; } } if (MI_IS_TITLE(mi)) { /* add space for the underlines */ switch (MST_TITLE_UNDERLINES(msp->menu)) { case 0: if (last_item_has_relief) MI_HEIGHT(mi) += relief_thickness; break; case 1: if (mi != MR_FIRST_ITEM(msp->menu)) { /* Space to draw the separator plus a gap above */ MI_HEIGHT(mi) += separator_height; } if (MI_NEXT_ITEM(mi) != NULL) { /* Space to draw the separator */ MI_HEIGHT(mi) += MENU_SEPARATOR_HEIGHT; } break; default: /* Space to draw n underlines. */ MI_HEIGHT(mi) += MENU_UNDERLINE_HEIGHT * MST_TITLE_UNDERLINES(msp->menu); if (last_item_has_relief) MI_HEIGHT(mi) += relief_thickness; break; } } for (i = 0; i < msp->used_mini_icons; i++) { if (MI_MINI_ICON(mi)[i]) { has_mini_icon = True; } if (MI_MINI_ICON(mi)[i] && MI_HEIGHT(mi) < MI_MINI_ICON(mi)[i]->height + relief_thickness) { MI_HEIGHT(mi) = MI_MINI_ICON(mi)[i]->height + relief_thickness; } } if (MI_PICTURE(mi)) { if ((MI_HAS_TEXT(mi) && msp->used_item_labels) || has_mini_icon) MI_HEIGHT(mi) += MI_PICTURE(mi)->height; else MI_HEIGHT(mi) = MI_PICTURE(mi)->height + relief_thickness; } y += MI_HEIGHT(mi); /* this item would have to be the last item, or else * we need to add a "More..." entry pointing to a new menu */ if (y + MST_BORDER_WIDTH(msp->menu) + ((MI_IS_SELECTABLE(mi)) ? relief_thickness : 0) > MR_SCREEN_HEIGHT(msp->menu)) { /* Item does not fit on screen anymore. */ Bool does_fit = False; char *t; char *tempname; MenuRoot *menuContinuation; /* Remove items form the menu until it fits (plus a 'More' entry). */ while (MI_PREV_ITEM(mi) != NULL) { /* Remove current item. */ y -= MI_HEIGHT(mi); mi = MI_PREV_ITEM(mi); cItems--; if (y + MST_BORDER_WIDTH(msp->menu) + simple_entry_height + 2 * relief_thickness <= MR_SCREEN_HEIGHT(msp->menu)) { /* ok, it fits now */ does_fit = True; break; } } if (!does_fit || MI_PREV_ITEM(mi) == NULL) { fvwm_msg(ERR, "size_menu_vertically", "Menu entry does not fit on screen"); /* leave a coredump */ abort(); exit(1); } t = EscapeString(MR_NAME(msp->menu), "\"", '\\'); tempname = (char *)safemalloc((10 + strlen(t)) * sizeof(char)); strcpy(tempname, "Popup \""); strcat(tempname, t); strcat(tempname, "$\""); free(t); /* NewMenuRoot inserts at the head of the list of menus but, we need it at the end */ /* (Give it just the name, which is 6 chars past the action since strlen("Popup ")==6 ) */ t = (char *)safemalloc(strlen(MR_NAME(msp->menu)) + 2); strcpy(t, MR_NAME(msp->menu)); strcat(t, "$"); menuContinuation = NewMenuRoot(t); free(t); MR_CONTINUATION_MENU(msp->menu) = menuContinuation; /* Now move this item and the remaining items into the new menu */ MR_FIRST_ITEM(menuContinuation) = MI_NEXT_ITEM(mi); MR_LAST_ITEM(menuContinuation) = MR_LAST_ITEM(msp->menu); MR_ITEMS(menuContinuation) = MR_ITEMS(msp->menu) - cItems; MI_PREV_ITEM(MI_NEXT_ITEM(mi)) = NULL; /* mi_prev is now the last item in the parent menu */ MR_LAST_ITEM(msp->menu) = mi; MR_ITEMS(msp->menu) = cItems; MI_NEXT_ITEM(mi) = NULL; /* use the same style for the submenu */ MR_STYLE(menuContinuation) = MR_STYLE(msp->menu); MR_IS_LEFT_TRIANGLE(menuContinuation) = MR_IS_LEFT_TRIANGLE(msp->menu); /* migo: propagate missing_submenu_func */ if (MR_MISSING_SUBMENU_FUNC(msp->menu)) { MR_MISSING_SUBMENU_FUNC(menuContinuation) = safestrdup(MR_MISSING_SUBMENU_FUNC(msp->menu)); } /* don't propagate sidepic, sidecolor, popup- and popdown actions */ /* And add the entry pointing to the new menu */ AddToMenu(msp->menu, "More&...", tempname, False /* no pixmap scan */, False); free(tempname); has_continuation_menu = True; } } /* for */ /* The menu may be empty here! */ if (MR_LAST_ITEM(msp->menu) != NULL && MI_IS_SELECTABLE(MR_LAST_ITEM(msp->menu))) y += relief_thickness; MR_HEIGHT(msp->menu) = y + MST_BORDER_WIDTH(msp->menu); return has_continuation_menu; } /**************************************************************************** * * Creates the window for the menu. * ****************************************************************************/ static void make_menu_window(MenuRoot *mr) { unsigned long valuemask; XSetWindowAttributes attributes; unsigned int w; unsigned int h; valuemask = CWBackPixel | CWEventMask | CWCursor | CWColormap | CWBorderPixel | CWSaveUnder; attributes.border_pixel = 0; attributes.colormap = Pcmap; attributes.background_pixel = (MST_HAS_MENU_CSET(mr)) ? Colorset[MST_CSET_MENU(mr)].bg : MST_MENU_COLORS(mr).back; attributes.event_mask = XEVMASK_MENUW; attributes.cursor = Scr.FvwmCursors[CRS_MENU]; attributes.save_under = True; if (MR_WINDOW(mr) != None) XDestroyWindow(dpy,MR_WINDOW(mr)); w = MR_WIDTH(mr); if (w == 0) w = 1; h = MR_HEIGHT(mr); if (h == 0) h = 1; MR_WINDOW(mr) = XCreateWindow(dpy, Scr.Root, 0, 0, w, h, (unsigned int) 0, Pdepth, InputOutput, Pvisual, valuemask, &attributes); XSaveContext(dpy,MR_WINDOW(mr),MenuContext,(caddr_t)mr); } /**************************************************************************** * * Generates the window for a menu * ****************************************************************************/ static void make_menu(MenuRoot *mr, MenuParameters *pmp) { MenuSizingParameters msp; Bool has_continuation_menu = False; #if 0 if (!Scr.flags.windows_captured) { return; } #endif if (MR_MAPPED_COPIES(mr) > 0) { return; } merge_continuation_menus(mr); do { memset(&msp, 0, sizeof(MenuSizingParameters)); msp.menu = mr; calculate_item_sizes(&msp); /* Call size_menu_horizontally first because it calculated some values * used by size_menu_vertically. */ size_menu_horizontally(&msp); has_continuation_menu = size_menu_vertically(&msp); /* repeat this step if the menu was split */ } while (has_continuation_menu); MR_XANIMATION(mr) = 0; memset(&(MR_DYNAMIC_FLAGS(mr)), 0, sizeof(MR_DYNAMIC_FLAGS(mr))); /* create a new window for the menu */ make_menu_window(mr); MR_IS_UPDATED(mr) = 0; return; } /*********************************************************************** * Procedure: * scanForHotkeys - Look for hotkey markers in a MenuItem * (pete@tecc.co.uk) * * Inputs: * it - MenuItem to scan * column - The column number in which to look for a hotkey. * ***********************************************************************/ static void scanForHotkeys(MenuItem *it, int column) { char *start, *txt; start = MI_LABEL(it)[column]; /* Get start of string */ for (txt = start; *txt != '\0'; txt++) { /* Scan whole string */ if (*txt == '&') { /* A hotkey marker? */ if (txt[1] == '&') { /* Just an escaped & */ char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } else { /* It's a hot key marker - work out the offset value */ MI_HOTKEY_COFFSET(it) = txt - start; MI_HOTKEY_COLUMN(it) = column; MI_HAS_HOTKEY(it) = (txt[1] != '\0'); MI_IS_HOTKEY_AUTOMATIC(it) = 0; for (; *txt != '\0'; txt++) { /* Copy down.. */ txt[0] = txt[1]; } return; /* Only one hotkey per item... */ } } } return; } /* Side picture support: this scans for a color int the menu name for colorization */ static void scanForColor(char *instring, Pixel *p, Bool *flag, char identifier) { char *tstart, *txt, *save_instring, *name; int i; *flag = False; /* save instring in case can't find pixmap */ save_instring = (char *)safemalloc(strlen(instring)+1); name = (char *)safemalloc(strlen(instring)+1); strcpy(save_instring,instring); /* Scan whole string */ for (txt = instring; *txt != '\0'; txt++) { /* A hotkey marker? */ if (*txt == identifier) { /* Just an escaped '^' */ if (txt[1] == identifier) { char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } /* It's a hot key marker - work out the offset value */ tstart = txt; txt++; i=0; while((*txt != identifier)&&(*txt != '\0')) { name[i] = *txt; txt++; i++; } name[i] = 0; *p = GetColor(name); *flag = True; if(*txt != '\0') txt++; while(*txt != '\0') *tstart++ = *txt++; *tstart = 0; break; } } free(name); free(save_instring); return; } static Bool scanForPixmap(char *instring, Picture **p, char identifier) { char *tstart, *txt, *name; int i; Picture *pp; *p = NULL; if (!instring) { return False; } name = (char *)safemalloc(strlen(instring)+1); /* Scan whole string */ for (txt = instring; *txt != '\0'; txt++) { /* A hotkey marker? */ if (*txt == identifier) { /* Just an escaped & */ if (txt[1] == identifier) { char *tmp; /* Copy the string down over it */ for (tmp = txt; *tmp != '\0'; tmp++) tmp[0] = tmp[1]; continue; /* ...And skip to the key char */ } /* It's a hot key marker - work out the offset value */ tstart = txt; txt++; i=0; while ((*txt != identifier)&&(*txt != '\0')) { name[i] = *txt; txt++; i++; } name[i] = 0; /* Next, check for a color pixmap */ pp = CachePicture(dpy, Scr.NoFocusWin, NULL, name, Scr.ColorLimit); if (*txt != '\0') txt++; while (*txt != '\0') { *tstart++ = *txt++; } *tstart = 0; if (pp) *p = pp; else fvwm_msg(WARN, "scanForPixmap", "Couldn't load image from %s", name); break; } } free(name); return (*p != NULL); } /* FollowMenuContinuations * Given an menu root, return the menu root to add to by * following continuation links until there are no more */ MenuRoot *FollowMenuContinuations(MenuRoot *mr, MenuRoot **pmrPrior ) { *pmrPrior = NULL; while ((mr != NULL) && (MR_CONTINUATION_MENU(mr) != NULL)) { *pmrPrior = mr; mr = MR_CONTINUATION_MENU(mr); } return mr; } /*********************************************************************** * * Procedure: * AddToMenu - add an item to a root menu * * Returned Value: * (MenuItem *) * * Inputs: * menu - pointer to the root menu to add the item * item - the text to appear in the menu * action - the string to possibly execute * * ckh - need to add boolean to say whether or not to expand for pixmaps, * so built in window list can handle windows w/ * and % in title. * ***********************************************************************/ void AddToMenu(MenuRoot *mr, char *item, char *action, Bool fPixmapsOk, Bool fNoPlus) { MenuItem *tmp; char *start; char *end; char *token = NULL; char *option = NULL; int i; short current_mini_icon = 0; if (MR_MAPPED_COPIES(mr) > 0) { /* whoa, we can't handle *everything* */ return; } if ((item == NULL || *item == 0) && (action == NULL || *action == 0) && fNoPlus) return; /* empty items screw up our menu when painted, so we replace them with a * separator */ if (item == NULL) item = ""; /*************************************************************** * Handle dynamic actions ***************************************************************/ if (StrEquals(item, "DynamicPopupAction")) { if (MR_POPUP_ACTION(mr)) free(MR_POPUP_ACTION(mr)); if (!action || *action == 0) MR_POPUP_ACTION(mr) = NULL; else MR_POPUP_ACTION(mr) = stripcpy(action); return; } else if (StrEquals(item, "DynamicPopdownAction")) { if (MR_POPDOWN_ACTION(mr)) free(MR_POPDOWN_ACTION(mr)); if (!action || *action == 0) MR_POPDOWN_ACTION(mr) = NULL; else MR_POPDOWN_ACTION(mr) = stripcpy(action); return; } else if (StrEquals(item, "MissingSubmenuFunction")) { if (MR_MISSING_SUBMENU_FUNC(mr)) free(MR_MISSING_SUBMENU_FUNC(mr)); if (!action || *action == 0) MR_MISSING_SUBMENU_FUNC(mr) = NULL; else MR_MISSING_SUBMENU_FUNC(mr) = stripcpy(action); return; } /*************************************************************** * Parse the action ***************************************************************/ if (action == NULL || *action == 0) action = "Nop"; GetNextToken(GetNextToken(action, &token), &option); tmp = (MenuItem *)safemalloc(sizeof(MenuItem)); memset(tmp, 0, sizeof(MenuItem)); if (MR_FIRST_ITEM(mr) == NULL) { MR_FIRST_ITEM(mr) = tmp; MR_LAST_ITEM(mr) = tmp; MI_NEXT_ITEM(tmp) = NULL; MI_PREV_ITEM(tmp) = NULL; } else if (StrEquals(token, "title") && option && StrEquals(option, "top")) { if (MI_IS_TITLE(MR_FIRST_ITEM(mr))) { if (MR_FIRST_ITEM(mr) == MR_LAST_ITEM(mr)) MR_LAST_ITEM(mr) = tmp; MI_PREV_ITEM(MI_NEXT_ITEM(MR_FIRST_ITEM(mr))) = tmp; MI_NEXT_ITEM(tmp) = MI_NEXT_ITEM(MR_FIRST_ITEM(mr)); FreeMenuItem(MR_FIRST_ITEM(mr)); } else { MI_PREV_ITEM(MR_FIRST_ITEM(mr)) = tmp; MI_NEXT_ITEM(tmp) = MR_FIRST_ITEM(mr); } MI_PREV_ITEM(tmp) = NULL; MR_FIRST_ITEM(mr) = tmp; } else { MI_NEXT_ITEM(MR_LAST_ITEM(mr)) = tmp; MI_PREV_ITEM(tmp) = MR_LAST_ITEM(mr); MR_LAST_ITEM(mr) = tmp; } if (token) free(token); if (option) free(option); MI_ACTION(tmp) = stripcpy(action); /*************************************************************** * Parse the labels ***************************************************************/ start = item; end = item; for (i = 0; i < MAX_MENU_ITEM_LABELS; i++, start = end) { /* Read label up to next tab. */ if (*end) { if (i < MAX_MENU_ITEM_LABELS - 1) { while (*end && *end != '\t') /* seek next tab or end of string */ end++; } else { /* remove all tabs in last label */ while (*end) { if (*end == '\t') *end = ' '; end++; } } /* Copy the label. */ MI_LABEL(tmp)[i] = safemalloc(end - start + 1); strncpy(MI_LABEL(tmp)[i], start, end - start); (MI_LABEL(tmp)[i])[end - start] = 0; if (*end == '\t') { /* skip the tab */ end++; } } else { MI_LABEL(tmp)[i] = NULL; } /* Parse the label. */ if (MI_LABEL(tmp)[i] != NULL) { if (fPixmapsOk) { if(!MI_PICTURE(tmp)) { if (scanForPixmap(MI_LABEL(tmp)[i], &MI_PICTURE(tmp), '*')) MI_HAS_PICTURE(tmp) = True; } while (current_mini_icon < MAX_MENU_ITEM_MINI_ICONS) { if (scanForPixmap( MI_LABEL(tmp)[i], &(MI_MINI_ICON(tmp)[current_mini_icon]), '%')) { current_mini_icon++; MI_HAS_PICTURE(tmp) = True; } else break; } } if (!MI_HAS_HOTKEY(tmp)) { /* pete@tecc.co.uk */ scanForHotkeys(tmp, i); if (!MI_HAS_HOTKEY(tmp) && *MI_LABEL(tmp)[i] != 0) { MI_HOTKEY_COFFSET(tmp) = 0; MI_HOTKEY_COLUMN(tmp) = i; MI_HAS_HOTKEY(tmp) = 1; MI_IS_HOTKEY_AUTOMATIC(tmp) = 1; } } if (*(MI_LABEL(tmp)[i])) { MI_HAS_TEXT(tmp) = True; } else { free(MI_LABEL(tmp)[i]); MI_LABEL(tmp)[i] = NULL; } } MI_LABEL_STRLEN(tmp)[i] = (MI_LABEL(tmp)[i]) ? strlen(MI_LABEL(tmp)[i]) : 0; } /* for */ /*************************************************************** * Set the type flags ***************************************************************/ find_func_type(MI_ACTION(tmp), &(MI_FUNC_TYPE(tmp)), NULL); switch (MI_FUNC_TYPE(tmp)) { case F_POPUP: MI_IS_POPUP(tmp) = True; case F_WINDOWLIST: case F_STAYSUP: MI_IS_MENU(tmp) = True; break; case F_TITLE: MI_IS_TITLE(tmp) = True; break; default: break; } MI_IS_SEPARATOR(tmp) = (!MI_HAS_TEXT(tmp) && !MI_HAS_PICTURE(tmp)); if (MI_IS_SEPARATOR(tmp)) { /* An empty title is handled like a separator. */ MI_IS_TITLE(tmp) = False; } MI_IS_SELECTABLE(tmp) = ((MI_HAS_TEXT(tmp) || MI_HAS_PICTURE(tmp)) && !MI_IS_TITLE(tmp)); /*************************************************************** * misc stuff ***************************************************************/ MR_ITEMS(mr)++; MR_IS_UPDATED(mr) = 1; } /*********************************************************************** * * Procedure: * NewMenuRoot - create a new menu root * * Returned Value: * (MenuRoot *) * * Inputs: * name - the name of the menu root * ***********************************************************************/ MenuRoot *NewMenuRoot(char *name) { MenuRoot *mr; Bool flag; mr = (MenuRoot *)safemalloc(sizeof(MenuRoot)); mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic)); mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic)); memset(mr->s, 0, sizeof(MenuRootStatic)); memset(mr->d, 0, sizeof(MenuRootDynamic)); MR_NEXT_MENU(mr) = Menus.all; MR_NAME(mr) = safestrdup(name); MR_WINDOW(mr) = None; scanForPixmap(MR_NAME(mr), &MR_SIDEPIC(mr), '@'); scanForColor(MR_NAME(mr), &MR_SIDECOLOR(mr), &flag,'^'); MR_HAS_SIDECOLOR(mr) = flag; MR_STYLE(mr) = Menus.DefaultStyle; MR_ORIGINAL_MENU(mr) = mr; MR_COPIES(mr) = 1; MR_IS_UPDATED(mr) = 1; Menus.all = mr; return mr; } /*********************************************************************** * * Procedure: * NewMenuRoot - creates a new instance of an existing menu * * Returned Value: * (MenuRoot *) * * Inputs: * menu - the MenuRoot structure of the existing menu * ***********************************************************************/ static MenuRoot *copy_menu_root(MenuRoot *mr) { MenuRoot *tmp; if (!mr || MR_COPIES(mr) >= MAX_MENU_COPIES) return NULL; tmp = (MenuRoot *)safemalloc(sizeof(MenuRoot)); tmp->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic)); memset(tmp->d, 0, sizeof(MenuRootDynamic)); tmp->s = mr->s; MR_COPIES(mr)++; MR_ORIGINAL_MENU(tmp) = MR_ORIGINAL_MENU(mr); MR_CONTINUATION_MENU(tmp) = MR_CONTINUATION_MENU(mr); MR_NEXT_MENU(tmp) = MR_NEXT_MENU(mr); MR_NEXT_MENU(mr) = tmp; MR_WINDOW(tmp) = None; memset(&(MR_DYNAMIC_FLAGS(tmp)), 0, sizeof(MR_DYNAMIC_FLAGS(tmp))); return tmp; } void SetMenuCursor(Cursor cursor) { MenuRoot *mr; mr = Menus.all; for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr)) if (MR_WINDOW(mr)) XDefineCursor(dpy, MR_WINDOW(mr), cursor); } /* * ------------------------ Menu and Popup commands -------------------------- */ static void menu_func(F_CMD_ARGS, Bool fStaysUp) { MenuRoot *menu; char *ret_action = NULL; MenuOptions mops; char *menu_name = NULL; XEvent *teventp; MenuParameters mp; MenuReturn mret; FvwmWindow *fw; int tc; extern FvwmWindow *Tmp_win; extern FvwmWindow *ButtonWindow; extern int Context; memset(&mops, 0, sizeof(mops)); memset(&mret, 0, sizeof(MenuReturn)); action = GetNextToken(action,&menu_name); action = get_menu_options(action, w, tmp_win, eventp, NULL, NULL, &mops); while (action && *action && isspace((unsigned char)*action)) action++; if (action && *action == 0) action = NULL; menu = FindPopup(menu_name); if(menu == NULL) { if(menu_name) { fvwm_msg(ERR,"menu_func","No such menu %s",menu_name); free(menu_name); } return; } if (menu_name && set_repeat_data(menu_name, (fStaysUp) ? REPEAT_MENU : REPEAT_POPUP,NULL)) free(menu_name); if (!action && eventp && eventp->type == KeyPress) teventp = (XEvent *)1; else teventp = eventp; memset(&mp, 0, sizeof(mp)); mp.menu = menu; mp.parent_menu = NULL; mp.parent_item = NULL; fw = (tmp_win != None) ? tmp_win : Tmp_win; mp.pTmp_win = &fw; mp.button_window = ButtonWindow; tc = Context; mp.pcontext = &tc; mp.flags.has_default_action = (action != NULL); mp.flags.is_menu_from_frame_or_window_or_titlebar = False; mp.flags.is_sticky = fStaysUp; mp.flags.is_submenu = False; mp.flags.is_already_mapped = False; mp.eventp = teventp; mp.pops = &mops; mp.ret_paction = &ret_action; mp.event_propagate_to_submenu = NULL; do_menu(&mp, &mret); if (mret.rc == MENU_DOUBLE_CLICKED && action) { old_execute_function(F_PASS_EXEC_ARGS, 0, NULL); } } /* the function for the "Popup" command */ void CMD_Popup(F_CMD_ARGS) { menu_func(F_PASS_ARGS, False); } /* the function for the "Menu" command */ void CMD_Menu(F_CMD_ARGS) { menu_func(F_PASS_ARGS, True); } /* * -------------------------- Menu style commands ---------------------------- */ static void FreeMenuFace(Display *dpy, MenuFace *mf) { switch (mf->type) { case GradientMenu: /* - should we check visual is not TrueColor before doing this? * * XFreeColors(dpy, PictureCMap, * ms->u.grad.pixels, ms->u.grad.npixels, * AllPlanes); */ free(mf->u.grad.pixels); mf->u.grad.pixels = NULL; break; case PixmapMenu: case TiledPixmapMenu: if (mf->u.p) DestroyPicture(dpy, mf->u.p); mf->u.p = NULL; break; case SolidMenu: FreeColors(&mf->u.back, 1); default: break; } mf->type = SimpleMenu; } /***************************************************************************** * * Reads a menu face line into a structure (veliaa@rpi.edu) * ****************************************************************************/ static Boolean ReadMenuFace(char *s, MenuFace *mf, int verbose) { char *style; char *token; char *action = s; s = GetNextToken(s, &style); if (style && strncasecmp(style, "--", 2) == 0) { free(style); return True; } FreeMenuFace(dpy, mf); mf->type = SimpleMenu; /* determine menu style */ if (!style) return True; else if (StrEquals(style,"Solid")) { s = GetNextToken(s, &token); if (token) { mf->type = SolidMenu; mf->u.back = GetColor(token); free(token); } else { if(verbose) fvwm_msg(ERR, "ReadMenuFace", "no color given for Solid face type: %s", action); free(style); return False; } } else if (StrEquals(style+1, "Gradient")) { char **s_colors; int npixels, nsegs, *perc; Pixel *pixels; if (!IsGradientTypeSupported(style[0])) return False; /* translate the gradient string into an array of colors etc */ npixels = ParseGradient(s, NULL, &s_colors, &perc, &nsegs); if (npixels <= 0) return False; /* grab the colors */ pixels = AllocAllGradientColors(s_colors, perc, nsegs, npixels); if (pixels == None) return False; mf->u.grad.pixels = pixels; mf->u.grad.npixels = npixels; mf->type = GradientMenu; mf->gradient_type = toupper(style[0]); } else if (StrEquals(style,"Pixmap") || StrEquals(style,"TiledPixmap")) { s = GetNextToken(s, &token); if (token) { mf->u.p = CachePicture(dpy, Scr.NoFocusWin, NULL, token, Scr.ColorLimit); if (mf->u.p == NULL) { if(verbose) fvwm_msg(ERR, "ReadMenuFace", "couldn't load pixmap %s", token); free(token); free(style); return False; } free(token); mf->type = (StrEquals(style,"TiledPixmap")) ? TiledPixmapMenu : PixmapMenu; } else { if(verbose) fvwm_msg(ERR, "ReadMenuFace", "missing pixmap name for style %s", style); free(style); return False; } } else { if(verbose) fvwm_msg(ERR, "ReadMenuFace", "unknown style %s: %s", style, action); free(style); return False; } free(style); return True; } static MenuStyle *FindMenuStyle(char *name) { MenuStyle *ms = Menus.DefaultStyle; while(ms) { if(strcasecmp(ST_NAME(ms),name)==0) return ms; ms = ST_NEXT_STYLE(ms); } return NULL; } static void FreeMenuStyle(MenuStyle *ms) { MenuRoot *mr; MenuStyle *before = Menus.DefaultStyle; if (!ms) return; mr = Menus.all; while(mr) { if(MR_STYLE(mr) == ms) MR_STYLE(mr) = Menus.DefaultStyle; mr = MR_NEXT_MENU(mr); } if(ST_MENU_GC(ms)) XFreeGC(dpy, ST_MENU_GC(ms)); if(ST_MENU_ACTIVE_GC(ms)) XFreeGC(dpy, ST_MENU_ACTIVE_GC(ms)); if(ST_MENU_ACTIVE_BACK_GC(ms)) XFreeGC(dpy, ST_MENU_ACTIVE_BACK_GC(ms)); if(ST_MENU_ACTIVE_RELIEF_GC(ms)) XFreeGC(dpy, ST_MENU_ACTIVE_RELIEF_GC(ms)); if(ST_MENU_ACTIVE_SHADOW_GC(ms)) XFreeGC(dpy, ST_MENU_ACTIVE_SHADOW_GC(ms)); if(ST_MENU_RELIEF_GC(ms)) XFreeGC(dpy, ST_MENU_RELIEF_GC(ms)); if(ST_MENU_STIPPLE_GC(ms)) XFreeGC(dpy, ST_MENU_STIPPLE_GC(ms)); if(ST_MENU_SHADOW_GC(ms)) XFreeGC(dpy, ST_MENU_SHADOW_GC(ms)); if (ST_SIDEPIC(ms)) DestroyPicture(dpy, ST_SIDEPIC(ms)); if (ST_HAS_SIDE_COLOR(ms) == 1) FreeColors(&ST_SIDE_COLOR(ms), 1); if (ST_PSTDFONT(ms) && ST_PSTDFONT(ms) != &Scr.DefaultFont) { FreeFvwmFont(dpy, ST_PSTDFONT(ms)); free(ST_PSTDFONT(ms)); } if (ST_ITEM_FORMAT(ms)) free(ST_ITEM_FORMAT(ms)); while(ST_NEXT_STYLE(before) != ms) /* Not too many checks, may segfaults in race conditions */ before = ST_NEXT_STYLE(before); ST_NEXT_STYLE(before) = ST_NEXT_STYLE(ms); free(ST_NAME(ms)); free(ms); } void CMD_DestroyMenuStyle(F_CMD_ARGS) { MenuStyle *ms = NULL; char *name = NULL; MenuRoot *mr; name = PeekToken(action, NULL); if (name == NULL) { fvwm_msg(ERR,"DestroyMenuStyle", "needs one parameter"); return; } ms = FindMenuStyle(name); if(ms == NULL) { return; } else if (ms == Menus.DefaultStyle) { fvwm_msg(ERR,"DestroyMenuStyle", "cannot destroy default menu style. " "To reset the default menu style use\n %s", DEFAULT_MENU_STYLE); return; } else if (ST_USAGE_COUNT(ms) != 0) { fvwm_msg(ERR,"DestroyMenuStyle", "menu style %s is in use", name); return; } else { FreeMenuFace(dpy, &ST_FACE(ms)); FreeMenuStyle(ms); } for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr)) { if (MR_STYLE(mr) == ms) { MR_STYLE(mr) = Menus.DefaultStyle; MR_IS_UPDATED(mr) = 1; } } } static void UpdateMenuStyle(MenuStyle *ms) { XGCValues gcv; unsigned long gcm; Pixel menu_fore; Pixel menu_back; Pixel relief_fore; Pixel relief_back; Pixel active_fore; Pixel active_back; Pixel active_relief_fore; Pixel active_relief_back; colorset_struct *menu_cs = &Colorset[ST_CSET_MENU(ms)]; colorset_struct *active_cs = &Colorset[ST_CSET_ACTIVE(ms)]; colorset_struct *greyed_cs = &Colorset[ST_CSET_GREYED(ms)]; if (ST_USAGE_COUNT(ms) != 0) { fvwm_msg(ERR,"UpdateMenuStyle", "menu style %s is in use", ST_NAME(ms)); return; } ST_IS_UPDATED(ms) = 1; /* calculate colors based on foreground */ if (!ST_HAS_ACTIVE_FORE(ms)) ST_MENU_ACTIVE_COLORS(ms).fore = ST_MENU_COLORS(ms).fore; /* calculate colors based on background */ if (!ST_HAS_ACTIVE_BACK(ms)) ST_MENU_ACTIVE_COLORS(ms).back = ST_MENU_COLORS(ms).back; if (!ST_HAS_STIPPLE_FORE(ms)) ST_MENU_STIPPLE_COLORS(ms).fore = ST_MENU_COLORS(ms).back; if(Pdepth > 2) { /* if not black and white */ ST_MENU_RELIEF_COLORS(ms).back = GetShadow(ST_MENU_COLORS(ms).back); ST_MENU_RELIEF_COLORS(ms).fore = GetHilite(ST_MENU_COLORS(ms).back); } else { /* black and white */ ST_MENU_RELIEF_COLORS(ms).back = GetColor(DEFAULT_SHADOW_COLOR); ST_MENU_RELIEF_COLORS(ms).fore = GetColor(DEFAULT_HILIGHT_COLOR); } ST_MENU_STIPPLE_COLORS(ms).back = ST_MENU_COLORS(ms).back; /* calculate some pixel values for convenience reasons */ if (ST_HAS_MENU_CSET(ms)) { menu_fore = menu_cs->fg; menu_back = menu_cs->bg; relief_fore = menu_cs->hilite; relief_back = menu_cs->shadow; active_relief_fore = menu_cs->hilite; active_relief_back = menu_cs->shadow; } else { menu_fore = ST_MENU_COLORS(ms).fore; menu_back = ST_MENU_COLORS(ms).back; relief_fore = ST_MENU_RELIEF_COLORS(ms).fore; relief_back = ST_MENU_RELIEF_COLORS(ms).back; active_relief_fore = ST_MENU_RELIEF_COLORS(ms).fore; active_relief_back = ST_MENU_RELIEF_COLORS(ms).back; } if (ST_HAS_ACTIVE_CSET(ms)) { active_fore = active_cs->fg; active_back = active_cs->bg; active_relief_fore = active_cs->hilite; active_relief_back = active_cs->shadow; } else { active_fore = (ST_HAS_ACTIVE_FORE(ms)) ? ST_MENU_ACTIVE_COLORS(ms).fore : menu_fore; active_back = (ST_HAS_ACTIVE_BACK(ms)) ? ST_MENU_ACTIVE_COLORS(ms).back : menu_back; } /* make GC's */ gcm = GCFunction|GCFont|GCLineWidth|GCForeground|GCBackground; gcv.font = ST_PSTDFONT(ms)->font->fid; gcv.function = GXcopy; gcv.line_width = 0; /* update relief gc */ gcv.foreground = relief_fore; gcv.background = relief_back; if (ST_MENU_RELIEF_GC(ms)) XChangeGC(dpy, ST_MENU_RELIEF_GC(ms), gcm, &gcv); else ST_MENU_RELIEF_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update shadow gc */ gcv.foreground = relief_back; gcv.background = relief_fore; if (ST_MENU_SHADOW_GC(ms)) XChangeGC(dpy, ST_MENU_SHADOW_GC(ms), gcm, &gcv); else ST_MENU_SHADOW_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update active gc */ gcv.foreground = active_fore; gcv.background = active_back; if(ST_MENU_ACTIVE_GC(ms)) XChangeGC(dpy, ST_MENU_ACTIVE_GC(ms), gcm, &gcv); else ST_MENU_ACTIVE_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update active relief gc */ gcv.foreground = active_relief_fore; gcv.background = active_relief_back; if(ST_MENU_ACTIVE_RELIEF_GC(ms)) XChangeGC(dpy, ST_MENU_ACTIVE_RELIEF_GC(ms), gcm, &gcv); else ST_MENU_ACTIVE_RELIEF_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update active shadow gc */ gcv.foreground = active_relief_back; gcv.background = active_relief_fore; if(ST_MENU_ACTIVE_SHADOW_GC(ms)) XChangeGC(dpy, ST_MENU_ACTIVE_SHADOW_GC(ms), gcm, &gcv); else ST_MENU_ACTIVE_SHADOW_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update active back gc */ gcv.foreground = active_back; gcv.background = active_fore; if (ST_MENU_ACTIVE_BACK_GC(ms)) XChangeGC(dpy, ST_MENU_ACTIVE_BACK_GC(ms), gcm, &gcv); else ST_MENU_ACTIVE_BACK_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update menu gc */ gcv.foreground = menu_fore; gcv.background = menu_back; if(ST_MENU_GC(ms)) XChangeGC(dpy, ST_MENU_GC(ms), gcm, &gcv); else ST_MENU_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); /* update stipple gc */ if(Pdepth < 2) { gcv.fill_style = FillStippled; gcv.stipple = Scr.gray_bitmap; gcm|=GCStipple|GCFillStyle; /* no need to reset fg/bg, FillStipple wins */ } else if (ST_HAS_GREYED_CSET(ms)) { gcv.foreground = greyed_cs->fg; gcv.background = greyed_cs->bg; } else { gcv.foreground = ST_MENU_STIPPLE_COLORS(ms).fore; gcv.background = ST_MENU_STIPPLE_COLORS(ms).back; } if (ST_MENU_STIPPLE_GC(ms)) XChangeGC(dpy, ST_MENU_STIPPLE_GC(ms), gcm, &gcv); else ST_MENU_STIPPLE_GC(ms) = fvwmlib_XCreateGC(dpy, Scr.NoFocusWin, gcm, &gcv); } void UpdateAllMenuStyles(void) { MenuStyle *ms; for (ms = Menus.DefaultStyle; ms; ms = ST_NEXT_STYLE(ms)) { UpdateMenuStyle(ms); } } void UpdateMenuColorset(int cset) { MenuStyle *ms; for (ms = Menus.DefaultStyle; ms; ms = ST_NEXT_STYLE(ms)) { if ((ST_HAS_MENU_CSET(ms) && ST_CSET_MENU(ms) == cset) || (ST_HAS_ACTIVE_CSET(ms) && ST_CSET_ACTIVE(ms) == cset) || (ST_HAS_GREYED_CSET(ms) && ST_CSET_GREYED(ms) == cset)) { UpdateMenuStyle(ms); } } } static void parse_vertical_spacing_line( char *args, signed char *above, signed char *below, signed char above_default, signed char below_default) { int val[2]; if (GetIntegerArguments(args, NULL, val, 2) != 2 || val[0] < MIN_VERTICAL_SPACING || val[0] > MAX_VERTICAL_SPACING || val[1] < MIN_VERTICAL_SPACING || val[1] > MAX_VERTICAL_SPACING) { /* illegal or missing parameters, return to default */ *above = above_default; *below = below_default; return; } *above = val[0]; *below = val[1]; return; } static int GetMenuStyleIndex(char *option) { char *optlist[] = { "fvwm", "mwm", "win", "Foreground", "Background", "Greyed", "HilightBack", "HilightBackOff", "ActiveFore", "ActiveForeOff", "Hilight3DThick", "Hilight3DThin", "Hilight3DOff", "Animation", "AnimationOff", "Font", "MenuFace", "PopupDelay", "PopupOffset", "TitleWarp", "TitleWarpOff", "TitleUnderlines0", "TitleUnderlines1", "TitleUnderlines2", "SeparatorsLong", "SeparatorsShort", "TrianglesSolid", "TrianglesRelief", "PopupImmediately", "PopupDelayed", "DoubleClickTime", "SidePic", "SideColor", "PopupAsRootmenu", "PopupAsSubmenu", "RemoveSubmenus", "HoldSubmenus", "SubmenusRight", "SubmenusLeft", "BorderWidth", "Hilight3DThickness", "ItemFormat", "AutomaticHotkeys", "AutomaticHotkeysOff", "VerticalItemSpacing", "VerticalTitleSpacing", "MenuColorset", "ActiveColorset", "GreyedColorset", "SelectOnRelease", "PopdownImmediately", "PopdownDelayed", "PopdownDelay", NULL }; return GetTokenIndex(option, optlist, 0, NULL); } static void NewMenuStyle(F_CMD_ARGS) { char *name; char *option = NULL; char *optstring = NULL; char *nextarg; char *args = NULL; char *arg1; MenuStyle *ms; MenuStyle *tmpms; Bool is_initialised = True; Bool has_gc_changed = False; Bool is_default_style = False; int val[2]; int n; FvwmFont new_font; int i; KeyCode keycode; action = GetNextToken(action, &name); if (!name) { fvwm_msg(ERR,"NewMenuStyle", "error in %s style specification",action); return; } ms = FindMenuStyle(name); if (ms && ST_USAGE_COUNT(ms) != 0) { fvwm_msg(ERR,"NewMenuStyle", "menu style %s is in use", name); return; } tmpms = (MenuStyle *)safemalloc(sizeof(MenuStyle)); memset(tmpms, 0, sizeof(MenuStyle)); if (ms) { /* copy the structure over our temporary menu face. */ memcpy(tmpms, ms, sizeof(MenuStyle)); if (ms == Menus.DefaultStyle) is_default_style = True; free(name); } else { ST_NAME(tmpms) = name; is_initialised = False; } ST_IS_UPDATED(tmpms) = 1; /* Parse the options. */ while (!is_initialised || (action && *action)) { if (!is_initialised) { /* some default configuration goes here for the new menu style */ ST_MENU_COLORS(tmpms).back = GetColor(DEFAULT_BACK_COLOR); ST_MENU_COLORS(tmpms).fore = GetColor(DEFAULT_FORE_COLOR); ST_PSTDFONT(tmpms) = &Scr.DefaultFont; ST_FACE(tmpms).type = SimpleMenu; ST_HAS_ACTIVE_FORE(tmpms) = 0; ST_HAS_ACTIVE_BACK(tmpms) = 0; ST_DO_POPUP_AS_ROOT_MENU(tmpms) = 0; ST_DOUBLE_CLICK_TIME(tmpms) = DEFAULT_MENU_CLICKTIME; ST_POPUP_DELAY(tmpms) = DEFAULT_POPUP_DELAY; ST_POPDOWN_DELAY(tmpms) = DEFAULT_POPDOWN_DELAY; has_gc_changed = True; option = "fvwm"; } else { /* Read next option specification (delimited by a comma or \0). */ args = action; action = GetQuotedString(action, &optstring, ",", NULL, NULL, NULL); if (!optstring) break; args = GetNextToken(optstring, &option); if (!option) { free(optstring); break; } nextarg = GetNextToken(args, &arg1); } switch((i = GetMenuStyleIndex(option))) { case 0: /* fvwm */ case 1: /* mwm */ case 2: /* win */ if (i == 0) { ST_POPUP_OFFSET_PERCENT(tmpms) = 67; ST_POPUP_OFFSET_ADD(tmpms) = 0; ST_DO_POPUP_IMMEDIATELY(tmpms) = 0; ST_DO_WARP_TO_TITLE(tmpms) = 1; ST_DO_UNMAP_SUBMENU_ON_POPDOWN(tmpms) = 0; ST_RELIEF_THICKNESS(tmpms) = 1; ST_TITLE_UNDERLINES(tmpms) = 1; ST_HAS_LONG_SEPARATORS(tmpms) = 0; ST_HAS_TRIANGLE_RELIEF(tmpms) = 1; ST_DO_HILIGHT(tmpms) = 0; } else if (i == 1) { ST_POPUP_OFFSET_PERCENT(tmpms) = 100; ST_POPUP_OFFSET_ADD(tmpms) = -DEFAULT_MENU_BORDER_WIDTH - 1; ST_DO_POPUP_IMMEDIATELY(tmpms) = 1; ST_DO_WARP_TO_TITLE(tmpms) = 0; ST_DO_UNMAP_SUBMENU_ON_POPDOWN(tmpms) = 0; ST_RELIEF_THICKNESS(tmpms) = 2; ST_TITLE_UNDERLINES(tmpms) = 2; ST_HAS_LONG_SEPARATORS(tmpms) = 1; ST_HAS_TRIANGLE_RELIEF(tmpms) = 1; ST_DO_HILIGHT(tmpms) = 0; } else /* i == 2 */ { ST_POPUP_OFFSET_PERCENT(tmpms) = 100; ST_POPUP_OFFSET_ADD(tmpms) = -DEFAULT_MENU_BORDER_WIDTH - 3; ST_DO_POPUP_IMMEDIATELY(tmpms) = 1; ST_DO_WARP_TO_TITLE(tmpms) = 0; ST_DO_UNMAP_SUBMENU_ON_POPDOWN(tmpms) = 1; ST_RELIEF_THICKNESS(tmpms) = 0; ST_TITLE_UNDERLINES(tmpms) = 1; ST_HAS_LONG_SEPARATORS(tmpms) = 0; ST_HAS_TRIANGLE_RELIEF(tmpms) = 0; ST_DO_HILIGHT(tmpms) = 1; } /* common settings */ ST_BORDER_WIDTH(tmpms) = DEFAULT_MENU_BORDER_WIDTH; ST_ITEM_GAP_ABOVE(tmpms) = DEFAULT_MENU_ITEM_TEXT_Y_OFFSET; ST_ITEM_GAP_BELOW(tmpms) = DEFAULT_MENU_ITEM_TEXT_Y_OFFSET2; ST_TITLE_GAP_ABOVE(tmpms) = DEFAULT_MENU_TITLE_TEXT_Y_OFFSET; ST_TITLE_GAP_BELOW(tmpms) = DEFAULT_MENU_TITLE_TEXT_Y_OFFSET2; ST_USE_LEFT_SUBMENUS(tmpms) = 0; ST_IS_ANIMATED(tmpms) = 0; ST_USE_AUTOMATIC_HOTKEYS(tmpms) = 0; FreeMenuFace(dpy, &ST_FACE(tmpms)); ST_FACE(tmpms).type = SimpleMenu; if (ST_PSTDFONT(tmpms) && ST_PSTDFONT(tmpms) != &Scr.DefaultFont) { FreeFvwmFont(dpy, ST_PSTDFONT(tmpms)); free(ST_PSTDFONT(tmpms)); } ST_PSTDFONT(tmpms) = &Scr.DefaultFont; has_gc_changed = True; if (ST_HAS_SIDE_COLOR(tmpms) == 1) { FreeColors(&ST_SIDE_COLOR(tmpms), 1); ST_HAS_SIDE_COLOR(tmpms) = 0; } ST_HAS_SIDE_COLOR(tmpms) = 0; if (ST_SIDEPIC(tmpms)) { DestroyPicture(dpy, ST_SIDEPIC(tmpms)); ST_SIDEPIC(tmpms) = NULL; } if (is_initialised == False) { /* now begin the real work */ is_initialised = True; continue; } break; case 3: /* Foreground */ FreeColors(&ST_MENU_COLORS(tmpms).fore, 1); if (arg1) ST_MENU_COLORS(tmpms).fore = GetColor(arg1); else ST_MENU_COLORS(tmpms).fore = GetColor(DEFAULT_FORE_COLOR); has_gc_changed = True; break; case 4: /* Background */ FreeColors(&ST_MENU_COLORS(tmpms).back, 1); if (arg1) ST_MENU_COLORS(tmpms).back = GetColor(arg1); else ST_MENU_COLORS(tmpms).back = GetColor(DEFAULT_BACK_COLOR); has_gc_changed = True; break; case 5: /* Greyed */ if (ST_HAS_STIPPLE_FORE(tmpms)) FreeColors(&ST_MENU_STIPPLE_COLORS(tmpms).fore, 1); if (arg1 == NULL) { ST_HAS_STIPPLE_FORE(tmpms) = 0; } else { ST_MENU_STIPPLE_COLORS(tmpms).fore = GetColor(arg1); ST_HAS_STIPPLE_FORE(tmpms) = 1; } has_gc_changed = True; break; case 6: /* HilightBack */ if (ST_HAS_ACTIVE_BACK(tmpms)) FreeColors(&ST_MENU_ACTIVE_COLORS(tmpms).back, 1); if (arg1 == NULL) { ST_HAS_ACTIVE_BACK(tmpms) = 0; } else { ST_MENU_ACTIVE_COLORS(tmpms).back = GetColor(arg1); ST_HAS_ACTIVE_BACK(tmpms) = 1; } ST_DO_HILIGHT(tmpms) = 1; has_gc_changed = True; break; case 7: /* HilightBackOff */ ST_DO_HILIGHT(tmpms) = 0; has_gc_changed = True; break; case 8: /* ActiveFore */ if (ST_HAS_ACTIVE_FORE(tmpms)) FreeColors(&ST_MENU_ACTIVE_COLORS(tmpms).fore, 1); if (arg1 == NULL) { ST_HAS_ACTIVE_FORE(tmpms) = 0; } else { ST_MENU_ACTIVE_COLORS(tmpms).fore = GetColor(arg1); ST_HAS_ACTIVE_FORE(tmpms) = 1; } has_gc_changed = True; break; case 9: /* ActiveForeOff */ ST_HAS_ACTIVE_FORE(tmpms) = 0; has_gc_changed = True; break; case 10: /* Hilight3DThick */ ST_RELIEF_THICKNESS(tmpms) = 2; break; case 11: /* Hilight3DThin */ ST_RELIEF_THICKNESS(tmpms) = 1; break; case 12: /* Hilight3DOff */ ST_RELIEF_THICKNESS(tmpms) = 0; break; case 13: /* Animation */ ST_IS_ANIMATED(tmpms) = 1; break; case 14: /* AnimationOff */ ST_IS_ANIMATED(tmpms) = 0; break; case 15: /* Font */ if (arg1 && !LoadFvwmFont(dpy, arg1, &new_font)) { fvwm_msg(ERR, "NewMenuStyle", "Couldn't load font '%s' or 'fixed'\n", arg1); break; } if (ST_PSTDFONT(tmpms) && ST_PSTDFONT(tmpms) != &Scr.DefaultFont) { FreeFvwmFont(dpy, ST_PSTDFONT(tmpms)); free(ST_PSTDFONT(tmpms)); } if (arg1 == NULL) { /* reset to screen font */ ST_PSTDFONT(tmpms) = &Scr.DefaultFont; } else { ST_PSTDFONT(tmpms) = (FvwmFont *)safemalloc(sizeof(FvwmFont)); *ST_PSTDFONT(tmpms) = new_font; } has_gc_changed = True; break; case 16: /* MenuFace */ while (args && *args != '\0' && isspace((unsigned char)*args)) args++; ReadMenuFace(args, &ST_FACE(tmpms), True); break; case 17: /* PopupDelay */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) ST_POPUP_DELAY(tmpms) = DEFAULT_POPUP_DELAY; else ST_POPUP_DELAY(tmpms) = (*val+9)/10; break; case 18: /* PopupOffset */ if ((n = GetIntegerArguments(args, NULL, val, 2)) == 0) { fvwm_msg(ERR,"NewMenuStyle", "PopupOffset requires one or two arguments"); } else { ST_POPUP_OFFSET_ADD(tmpms) = val[0]; if (n == 2 && val[1] <= 100 && val[1] >= 0) ST_POPUP_OFFSET_PERCENT(tmpms) = val[1]; else ST_POPUP_OFFSET_PERCENT(tmpms) = 100; } break; case 19: /* TitleWarp */ ST_DO_WARP_TO_TITLE(tmpms) = 1; break; case 20: /* TitleWarpOff */ ST_DO_WARP_TO_TITLE(tmpms) = 0; break; case 21: /* TitleUnderlines0 */ ST_TITLE_UNDERLINES(tmpms) = 0; break; case 22: /* TitleUnderlines1 */ ST_TITLE_UNDERLINES(tmpms) = 1; break; case 23: /* TitleUnderlines2 */ ST_TITLE_UNDERLINES(tmpms) = 2; break; case 24: /* SeparatorsLong */ ST_HAS_LONG_SEPARATORS(tmpms) = 1; break; case 25: /* SeparatorsShort */ ST_HAS_LONG_SEPARATORS(tmpms) = 0; break; case 26: /* TrianglesSolid */ ST_HAS_TRIANGLE_RELIEF(tmpms) = 0; break; case 27: /* TrianglesRelief */ ST_HAS_TRIANGLE_RELIEF(tmpms) = 1; break; case 28: /* PopupImmediately */ ST_DO_POPUP_IMMEDIATELY(tmpms) = 1; break; case 29: /* PopupDelayed */ ST_DO_POPUP_IMMEDIATELY(tmpms) = 0; break; case 30: /* DoubleClickTime */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) ST_DOUBLE_CLICK_TIME(tmpms) = DEFAULT_MENU_CLICKTIME; else ST_DOUBLE_CLICK_TIME(tmpms) = *val; break; case 31: /* SidePic */ if (ST_SIDEPIC(tmpms)) { DestroyPicture(dpy, ST_SIDEPIC(tmpms)); ST_SIDEPIC(tmpms) = NULL; } if (arg1) { ST_SIDEPIC(tmpms) = CachePicture(dpy, Scr.NoFocusWin, NULL, arg1, Scr.ColorLimit); if (!ST_SIDEPIC(tmpms)) fvwm_msg(WARN, "NewMenuStyle", "Couldn't find pixmap %s", arg1); } break; case 32: /* SideColor */ if (ST_HAS_SIDE_COLOR(tmpms) == 1) { FreeColors(&ST_SIDE_COLOR(tmpms), 1); ST_HAS_SIDE_COLOR(tmpms) = 0; } if (arg1) { ST_SIDE_COLOR(tmpms) = GetColor(arg1); ST_HAS_SIDE_COLOR(tmpms) = 1; } break; case 33: /* PopupAsRootmenu */ ST_DO_POPUP_AS_ROOT_MENU(tmpms) = 1; break; case 34: /* PopupAsSubmenu */ ST_DO_POPUP_AS_ROOT_MENU(tmpms) = 0; break; case 35: /* RemoveSubmenus */ ST_DO_UNMAP_SUBMENU_ON_POPDOWN(tmpms) = 1; break; case 36: /* HoldSubmenus */ ST_DO_UNMAP_SUBMENU_ON_POPDOWN(tmpms) = 0; break; case 37: /* SubmenusRight */ ST_USE_LEFT_SUBMENUS(tmpms) = 0; break; case 38: /* SubmenusLeft */ ST_USE_LEFT_SUBMENUS(tmpms) = 1; break; case 39: /* BorderWidth */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0 || *val > MAX_MENU_BORDER_WIDTH) ST_BORDER_WIDTH(tmpms) = DEFAULT_MENU_BORDER_WIDTH; else ST_BORDER_WIDTH(tmpms) = *val; break; case 40: /* Hilight3DThickness */ if (GetIntegerArguments(args, NULL, val, 1) > 0) { if (*val < 0) { *val = -*val; ST_IS_ITEM_RELIEF_REVERSED(tmpms) = 1; } else { ST_IS_ITEM_RELIEF_REVERSED(tmpms) = 0; } if (*val > MAX_MENU_ITEM_RELIEF_THICKNESS) *val = MAX_MENU_ITEM_RELIEF_THICKNESS; ST_RELIEF_THICKNESS(tmpms) = *val; } break; case 41: /* ItemFormat */ if (ST_ITEM_FORMAT(tmpms)) { free(ST_ITEM_FORMAT(tmpms)); ST_ITEM_FORMAT(tmpms) = NULL; } if (arg1) { ST_ITEM_FORMAT(tmpms) = safestrdup(arg1); } break; case 42: /* AutomaticHotkeys */ ST_USE_AUTOMATIC_HOTKEYS(tmpms) = 1; break; case 43: /* AutomaticHotkeysOff */ ST_USE_AUTOMATIC_HOTKEYS(tmpms) = 0; break; case 44: /* VerticalItemSpacing */ parse_vertical_spacing_line( args, &ST_ITEM_GAP_ABOVE(tmpms), &ST_ITEM_GAP_BELOW(tmpms), DEFAULT_MENU_ITEM_TEXT_Y_OFFSET, DEFAULT_MENU_ITEM_TEXT_Y_OFFSET2); break; case 45: /* VerticalTitleSpacing */ parse_vertical_spacing_line( args, &ST_TITLE_GAP_ABOVE(tmpms), &ST_TITLE_GAP_BELOW(tmpms), DEFAULT_MENU_TITLE_TEXT_Y_OFFSET, DEFAULT_MENU_TITLE_TEXT_Y_OFFSET2); break; case 46: /* MenuColorset */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) ST_HAS_MENU_CSET(tmpms) = 0; else { ST_HAS_MENU_CSET(tmpms) = 1; ST_CSET_MENU(tmpms) = *val; AllocColorset(*val); } has_gc_changed = True; break; case 47: /* ActiveColorset */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) { ST_HAS_ACTIVE_CSET(tmpms) = 0; } else { ST_HAS_ACTIVE_CSET(tmpms) = 1; ST_CSET_ACTIVE(tmpms) = *val; AllocColorset(*val); } has_gc_changed = True; break; case 48: /* GreyedColorset */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) ST_HAS_GREYED_CSET(tmpms) = 0; else { ST_HAS_GREYED_CSET(tmpms) = 1; ST_CSET_GREYED(tmpms) = *val; AllocColorset(*val); } has_gc_changed = True; break; case 49: /* SelectOnRelease */ keycode = 0; if (arg1) { keycode = XKeysymToKeycode(dpy, FvwmStringToKeysym(dpy, arg1)); } ST_SELECT_ON_RELEASE_KEY(tmpms) = keycode; break; case 50: /* PopdownImmediately */ ST_DO_POPDOWN_IMMEDIATELY(tmpms) = 1; break; case 51: /* PopdownDelayed */ ST_DO_POPDOWN_IMMEDIATELY(tmpms) = 0; break; case 52: /* PopdownDelay */ if (GetIntegerArguments(args, NULL, val, 1) == 0 || *val < 0) ST_POPDOWN_DELAY(tmpms) = DEFAULT_POPDOWN_DELAY; else ST_POPDOWN_DELAY(tmpms) = (*val+9)/10; break; #if 0 case 99: /* PositionHints */ /* to be implemented */ break; #endif default: fvwm_msg(ERR,"NewMenuStyle", "unknown option '%s'", option); break; } /* switch */ if (option) { free(option); option = NULL; } free(optstring); optstring = NULL; if (arg1) { free(arg1); arg1 = NULL; } } /* while */ if (has_gc_changed) { UpdateMenuStyle(tmpms); } /* if (has_gc_changed) */ if(Menus.DefaultStyle == NULL) { /* First MenuStyle MUST be the default style */ Menus.DefaultStyle = tmpms; ST_NEXT_STYLE(tmpms) = NULL; } else if (ms) { /* copy our new menu face over the old one */ memcpy(ms, tmpms, sizeof(MenuStyle)); free(tmpms); } else { MenuStyle *before = Menus.DefaultStyle; /* add a new menu face to list */ ST_NEXT_STYLE(tmpms) = NULL; while(ST_NEXT_STYLE(before)) before = ST_NEXT_STYLE(before); ST_NEXT_STYLE(before) = tmpms; } return; } void CMD_CopyMenuStyle(F_CMD_ARGS) { char *origname = NULL; char *destname = NULL; char *buffer; MenuStyle *origms; MenuStyle *destms; origname = PeekToken(action, &action); if (origname == NULL) { fvwm_msg(ERR,"CopyMenuStyle", "need two arguments"); return; } origms = FindMenuStyle(origname); if (!origms) { fvwm_msg(ERR,"CopyMenuStyle", "%s: no such menu style", origname); return; } destname = PeekToken(action, &action); if (destname == NULL) { fvwm_msg(ERR,"CopyMenuStyle", "need two arguments"); return; } if (action && *action) { fvwm_msg(ERR,"CopyMenuStyle", "too many arguments"); return; } destms = FindMenuStyle(destname); if (!destms) { /* create destms menu style */ buffer = (char *)safemalloc(strlen(destname) + 3); sprintf(buffer,"\"%s\"",destname); action = buffer; NewMenuStyle(F_PASS_ARGS); free(buffer); destms = FindMenuStyle(destname); if (!destms) { /* this must never happen */ fvwm_msg(ERR,"CopyMenuStyle", "impossible to create %s menu style", destname); return; } } if (strcasecmp("*",destname) == 0) { fvwm_msg(ERR,"CopyMenuStyle", "You cannot copy on the default menu style"); return; } if (strcasecmp(ST_NAME(origms),destname) == 0) { fvwm_msg(ERR,"CopyMenuStyle", "%s and %s identify the same menu style", ST_NAME(origms),destname); return; } if (ST_USAGE_COUNT(destms) != 0) { fvwm_msg(ERR,"CopyMenuStyle", "menu style %s is in use", destname); return; } /* Copy origms to destms, be award of all pointers in the MenuStyle strcture. Use the same order as in NewMenuStyle */ /* menu colors */ FreeColors(&ST_MENU_COLORS(destms).fore, 1); FreeColors(&ST_MENU_COLORS(destms).back, 1); memcpy(&ST_MENU_COLORS(destms),&ST_MENU_COLORS(origms),sizeof(ColorPair)); /* Greyed */ if (ST_HAS_STIPPLE_FORE(destms)) FreeColors(&ST_MENU_STIPPLE_COLORS(destms).fore, 1); ST_HAS_STIPPLE_FORE(destms) = ST_HAS_STIPPLE_FORE(origms); if (ST_HAS_STIPPLE_FORE(origms)) memcpy(&ST_MENU_STIPPLE_COLORS(destms).fore, &ST_MENU_STIPPLE_COLORS(origms).fore, sizeof(Pixel)); /* HilightBack */ if (ST_HAS_ACTIVE_BACK(destms)) FreeColors(&ST_MENU_ACTIVE_COLORS(destms).back, 1); ST_HAS_ACTIVE_BACK(destms) = ST_HAS_ACTIVE_BACK(origms); if (ST_HAS_ACTIVE_BACK(origms)) memcpy(&ST_MENU_ACTIVE_COLORS(destms).back, &ST_MENU_ACTIVE_COLORS(origms).back, sizeof(Pixel)); ST_DO_HILIGHT(destms) = ST_DO_HILIGHT(origms); /* ActiveFore */ if (ST_HAS_ACTIVE_FORE(destms)) FreeColors(&ST_MENU_ACTIVE_COLORS(destms).fore, 1); ST_HAS_ACTIVE_FORE(destms) = ST_HAS_ACTIVE_FORE(origms); if (ST_HAS_ACTIVE_FORE(origms)) memcpy(&ST_MENU_ACTIVE_COLORS(destms).fore, &ST_MENU_ACTIVE_COLORS(origms).fore, sizeof(Pixel)); /* Hilight3D */ ST_RELIEF_THICKNESS(destms) = ST_RELIEF_THICKNESS(origms); /* Animation */ ST_IS_ANIMATED(destms) = ST_IS_ANIMATED(origms); /* font */ if (ST_PSTDFONT(destms) && ST_PSTDFONT(destms) != &Scr.DefaultFont) { FreeFvwmFont(dpy, ST_PSTDFONT(destms)); free(ST_PSTDFONT(destms)); } if (ST_PSTDFONT(origms) && ST_PSTDFONT(origms) != &Scr.DefaultFont) { unsigned long value = 0; char *orig_font_name; FvwmFont new_font; Bool loaded = 0; if (XGetFontProperty(ST_PSTDFONT(origms)->font,XA_FONT,&value)) { orig_font_name = XGetAtomName(dpy, value); if (orig_font_name != NULL) { if (!LoadFvwmFont(dpy, orig_font_name, &new_font)) { fvwm_msg(ERR, "CopyMenuStyle", "Couldn't load font '%s' or 'fixed'\n", orig_font_name); } else { loaded = 1; } XFree(orig_font_name); } } if (loaded) { ST_PSTDFONT(destms) = (FvwmFont *)safemalloc(sizeof(FvwmFont)); *ST_PSTDFONT(destms) = new_font; } else { ST_PSTDFONT(destms) = &Scr.DefaultFont; fvwm_msg(ERR, "CopyMenuStyle", "Unable to find origin font name: Using Default Font\n"); } } else ST_PSTDFONT(destms) = &Scr.DefaultFont; /* MenuFace */ FreeMenuFace(dpy, &ST_FACE(destms)); ST_FACE(destms).type = SimpleMenu; switch (ST_FACE(origms).type) { case SolidMenu: memcpy(&ST_FACE(destms).u.back,&ST_FACE(origms).u.back,sizeof(Pixel)); ST_FACE(destms).type = SolidMenu; break; case GradientMenu: ST_FACE(destms).u.grad.pixels = (Pixel *)safemalloc(sizeof(Pixel) * ST_FACE(origms).u.grad.npixels); memcpy(ST_FACE(destms).u.grad.pixels,ST_FACE(origms).u.grad.pixels, sizeof(Pixel) * ST_FACE(origms).u.grad.npixels); ST_FACE(destms).u.grad.npixels = ST_FACE(origms).u.grad.npixels; ST_FACE(destms).type = GradientMenu; ST_FACE(destms).gradient_type = ST_FACE(origms).gradient_type; break; case PixmapMenu: case TiledPixmapMenu: ST_FACE(destms).u.p = CachePicture(dpy, Scr.NoFocusWin, NULL, ST_FACE(origms).u.p->name, Scr.ColorLimit); memcpy(&ST_FACE(destms).u.back,&ST_FACE(origms).u.back,sizeof(Pixel)); ST_FACE(destms).type = ST_FACE(origms).type; break; default: break; } /* PopupDelay */ ST_POPUP_DELAY(destms) = ST_POPUP_DELAY(origms); /* PopupOffset */ ST_POPUP_OFFSET_PERCENT(destms) = ST_POPUP_OFFSET_PERCENT(origms); ST_POPUP_OFFSET_ADD(destms) = ST_POPUP_OFFSET_ADD(origms); /* TitleWarp */ ST_DO_WARP_TO_TITLE(destms) = ST_DO_WARP_TO_TITLE(origms); /* TitleUnderlines */ ST_TITLE_UNDERLINES(destms) = ST_TITLE_UNDERLINES(origms); /* Separators */ ST_HAS_LONG_SEPARATORS(destms) = ST_HAS_LONG_SEPARATORS(origms); /* Triangles */ ST_HAS_TRIANGLE_RELIEF(destms) = ST_HAS_TRIANGLE_RELIEF(origms); /* PopupDelayed */ ST_DO_POPUP_IMMEDIATELY(destms) = ST_DO_POPUP_IMMEDIATELY(origms); /* DoubleClickTime */ ST_DOUBLE_CLICK_TIME(destms) = ST_DOUBLE_CLICK_TIME(origms); /* SidePic */ if (ST_SIDEPIC(destms)) { DestroyPicture(dpy, ST_SIDEPIC(destms)); ST_SIDEPIC(destms) = NULL; } if (ST_SIDEPIC(origms)) ST_SIDEPIC(destms) = CachePicture(dpy, Scr.NoFocusWin, NULL, ST_SIDEPIC(origms)->name, Scr.ColorLimit); /* side color */ if (ST_HAS_SIDE_COLOR(destms) == 1) FreeColors(&ST_SIDE_COLOR(destms), 1); ST_HAS_SIDE_COLOR(destms) = ST_HAS_SIDE_COLOR(origms); if (ST_HAS_SIDE_COLOR(origms) == 1) memcpy(&ST_SIDE_COLOR(destms),&ST_SIDE_COLOR(origms),sizeof(Pixel)); /* PopupAsRootmenu */ ST_DO_POPUP_AS_ROOT_MENU(destms) = ST_DO_POPUP_AS_ROOT_MENU(origms); /* RemoveSubmenus */ ST_DO_UNMAP_SUBMENU_ON_POPDOWN(destms) = ST_DO_UNMAP_SUBMENU_ON_POPDOWN(origms); /* SubmenusRight */ ST_USE_LEFT_SUBMENUS(destms) = ST_USE_LEFT_SUBMENUS(origms); /* BorderWidth */ ST_BORDER_WIDTH(destms) = ST_BORDER_WIDTH(origms); /* Hilight3DThickness */ ST_IS_ITEM_RELIEF_REVERSED(destms) = ST_IS_ITEM_RELIEF_REVERSED(origms); /* ItemFormat */ if (ST_ITEM_FORMAT(destms)) { free(ST_ITEM_FORMAT(destms)); ST_ITEM_FORMAT(destms) = NULL; } if (ST_ITEM_FORMAT(origms)) ST_ITEM_FORMAT(destms) = safestrdup(ST_ITEM_FORMAT(origms)); /* AutomaticHotkeys */ ST_USE_AUTOMATIC_HOTKEYS(destms) = ST_USE_AUTOMATIC_HOTKEYS(origms); /* Item and Title Spacing */ ST_ITEM_GAP_ABOVE(destms) = ST_ITEM_GAP_ABOVE(origms); ST_ITEM_GAP_BELOW(destms) = ST_ITEM_GAP_BELOW(origms); ST_TITLE_GAP_ABOVE(destms) = ST_TITLE_GAP_ABOVE(origms); ST_TITLE_GAP_BELOW(destms) = ST_TITLE_GAP_BELOW(origms); /* MenuColorset */ ST_HAS_MENU_CSET(destms) = ST_HAS_MENU_CSET(origms); ST_CSET_MENU(destms) = ST_CSET_MENU(origms); /* ActiveColorset */ ST_HAS_ACTIVE_CSET(destms) = ST_HAS_ACTIVE_CSET(origms); ST_CSET_ACTIVE(destms) = ST_CSET_ACTIVE(origms); /* MenuColorset */ ST_HAS_GREYED_CSET(destms) = ST_HAS_GREYED_CSET(origms); ST_CSET_GREYED(destms) = ST_CSET_GREYED(origms); /* SelectOnRelease */ ST_SELECT_ON_RELEASE_KEY(destms) = ST_SELECT_ON_RELEASE_KEY(origms); /* PopdownImmediately */ ST_DO_POPDOWN_IMMEDIATELY(destms) = ST_DO_POPDOWN_IMMEDIATELY(origms); /* PopdownDelay */ ST_POPDOWN_DELAY(destms) = ST_POPDOWN_DELAY(destms); UpdateMenuStyle(destms); } static void OldMenuStyle(F_CMD_ARGS) { char *buffer, *rest; char *fore, *back, *stipple, *font, *style, *animated; rest = GetNextToken(action,&fore); rest = GetNextToken(rest,&back); rest = GetNextToken(rest,&stipple); rest = GetNextToken(rest,&font); rest = GetNextToken(rest,&style); rest = GetNextToken(rest,&animated); if(!fore || !back || !stipple || !font || !style) { fvwm_msg(ERR,"OldMenuStyle", "error in %s style specification", action); } else { buffer = (char *)safemalloc(strlen(action) + 100); sprintf(buffer, "* \"%s\", Foreground \"%s\", Background \"%s\", " "Greyed \"%s\", Font \"%s\", \"%s\"", style, fore, back, stipple, font, (animated && StrEquals(animated, "anim")) ? "Animation" : "AnimationOff"); action = buffer; NewMenuStyle(F_PASS_ARGS); free(buffer); } if(fore) free(fore); if(back) free(back); if(stipple) free(stipple); if(font) free(font); if(style) free(style); if(animated) free(animated); } void CMD_MenuStyle(F_CMD_ARGS) { char *option; GetNextSimpleOption(SkipNTokens(action, 1), &option); if (option == NULL || GetMenuStyleIndex(option) != -1) NewMenuStyle(F_PASS_ARGS); else OldMenuStyle(F_PASS_ARGS); if (option) free(option); return; } /* This is called by the window list function */ void change_mr_menu_style(MenuRoot *mr, char *stylename) { MenuStyle *ms = NULL; ms = FindMenuStyle(stylename); if(ms == NULL) return; if (MR_MAPPED_COPIES(mr) != 0) { fvwm_msg(ERR,"ChangeMenuStyle", "menu %s is in use", MR_NAME(mr)); } else { MR_STYLE(mr) = ms; MR_IS_UPDATED(mr) = 1; } } void CMD_ChangeMenuStyle(F_CMD_ARGS) { char *name = NULL; char *menuname = NULL; MenuStyle *ms = NULL; MenuRoot *mr = NULL; name = PeekToken(action, &action); if (name == NULL) { fvwm_msg(ERR,"ChangeMenuStyle", "needs at least two parameters"); return; } ms = FindMenuStyle(name); if(ms == NULL) { fvwm_msg(ERR,"ChangeMenuStyle", "cannot find style %s", name); return; } menuname = PeekToken(action, &action); while(menuname && *menuname) { mr = FindPopup(menuname); if(mr == NULL) { fvwm_msg(ERR,"ChangeMenuStyle", "cannot find menu %s", menuname); break; } if (MR_MAPPED_COPIES(mr) != 0) { fvwm_msg(ERR,"ChangeMenuStyle", "menu %s is in use", menuname); } else { MR_STYLE(mr) = ms; MR_IS_UPDATED(mr) = 1; } menuname = PeekToken(action, &action); } } void CMD_AddToMenu(F_CMD_ARGS) { MenuRoot *mr; MenuRoot *mrPrior; char *token, *rest,*item; token = PeekToken(action, &rest); if (!token) return; mr = FindPopup(token); if (mr && MR_MAPPED_COPIES(mr) != 0) { fvwm_msg(ERR,"add_item_to_menu", "menu %s is in use", token); return; } mr = FollowMenuContinuations(FindPopup(token), &mrPrior); if (mr == NULL) mr = NewMenuRoot(token); /* Set + state to last menu */ set_last_added_item(ADDED_MENU, mr); rest = GetNextToken(rest, &item); AddToMenu(mr, item, rest, True /* pixmap scan */, True); if (item) free(item); return; } void add_another_menu_item(char *action) { MenuRoot *mr; MenuRoot *mrPrior; char *rest,*item; mr = FollowMenuContinuations(Scr.last_added_item.item, &mrPrior); if(mr == NULL) return; if (MR_MAPPED_COPIES(mr) != 0) { fvwm_msg(ERR,"add_another_menu_item", "menu is in use"); return; } rest = GetNextToken(action,&item); AddToMenu(mr, item,rest,True /* pixmap scan */, False); if (item) free(item); return; } /* * ----------------------------- Position hints ------------------------------- */ static int float_to_int_with_tolerance(float f) { int low; if (f < 0) low = (int)(f - ROUNDING_ERROR_TOLERANCE); else low = (int)(f + ROUNDING_ERROR_TOLERANCE); if ((int)f != low) return low; else return (int)(f); } static void get_xy_from_position_hints( MenuPosHints *ph, int width, int height, int context_width, Bool do_reverse_x, int *ret_x, int *ret_y) { float x_add; float y_add; *ret_x = ph->x; *ret_y = ph->y; if (ph->is_menu_relative) { if (do_reverse_x) { *ret_x -= ph->x_offset; x_add = width * (-1.0 - ph->x_factor) + ph->menu_width * (1.0 - ph->context_x_factor); } else { *ret_x += ph->x_offset; x_add = width * ph->x_factor + ph->menu_width * ph->context_x_factor; } y_add = height * ph->y_factor; } else { x_add = width * ph->x_factor; y_add = height * ph->y_factor; } *ret_x += float_to_int_with_tolerance(x_add); *ret_y += float_to_int_with_tolerance(y_add); } /***************************************************************************** * Used by get_menu_options * * The vars are named for the x-direction, but this is used for both x and y *****************************************************************************/ static char *get_one_menu_position_argument( char *action, int x, int w, int *pFinalX, int *x_offset, float *width_factor, float *context_width_factor, Bool *is_menu_relative) { char *token, *orgtoken, *naction; char c; int val; int chars; float fval; float factor = (float)w/100; float x_add = 0; naction = GetNextToken(action, &token); if (token == NULL) return action; orgtoken = token; *pFinalX = x; *x_offset = 0; *width_factor = 0.0; *context_width_factor = 0.0; if (sscanf(token,"o%d%n", &val, &chars) >= 1) { fval = val; token += chars; x_add += fval*factor; *width_factor -= fval / 100.0; *context_width_factor += fval / 100.0; } else if (token[0] == 'c') { token++; x_add += ((float)w) / 2.0; *width_factor -= 0.5; *context_width_factor += 0.5; } while (*token != 0) { if (sscanf(token,"%d%n", &val, &chars) >= 1) { fval = (float)val; token += chars; if (sscanf(token,"%c", &c) == 1) { switch (c) { case 'm': token++; *width_factor += fval / 100.0; *is_menu_relative = True; break; case 'p': token++; x_add += val; *x_offset += val; break; default: x_add += fval * factor; *context_width_factor += fval / 100.0; break; } } else { x_add += fval * factor; *context_width_factor += fval / 100.0; } } else { naction = action; break; } } *pFinalX += float_to_int_with_tolerance(x_add); free(orgtoken); return naction; } /***************************************************************************** * get_menu_options is used for Menu, Popup and WindowList * It parses strings matching * * [ [context-rectangle] x y ] [special-options] [other arguments] * * and returns a pointer to the first part of the input string that doesn't * match this syntax. * * See documentation for a detailed description. ****************************************************************************/ char *get_menu_options( char *action, Window w, FvwmWindow *tmp_win, XEvent *e, MenuRoot *mr, MenuItem *mi, MenuOptions *pops) { char *tok = NULL; char *naction = action; char *taction; int x; int y; int button; int gflags; unsigned int width; unsigned int height; int dummy_int; float dummy_float; Bool dummy_flag; Window context_window = None; Bool fHasContext, fUseItemOffset, fRectangleContext, fXineramaRoot; Bool fValidPosHints = last_saved_pos_hints.flags.is_last_menu_pos_hints_valid; Bool is_action_empty = False; /* If this is set we may want to reverse the position hints, so don't sum up * the totals right now. This is useful for the SubmenusLeft style. */ fXineramaRoot = False; last_saved_pos_hints.flags.is_last_menu_pos_hints_valid = False; if (pops == NULL) { fvwm_msg(ERR,"get_menu_options","no MenuOptions pointer passed"); return action; } taction = action; memset(&(pops->flags), 0, sizeof(pops->flags)); pops->flags.has_poshints = 0; if (!action || *action == 0) { is_action_empty = True; } while (action != NULL && *action != 0) { /* ^ just to be able to jump to end of loop without 'goto' */ gflags = NoValue; pops->pos_hints.is_relative = False; pops->pos_hints.menu_width = 0; pops->pos_hints.is_menu_relative = False; /* parse context argument (if present) */ naction = GetNextToken(taction, &tok); if (!tok) { /* no context string */ fHasContext = False; is_action_empty = True; break; } pops->pos_hints.is_relative = True; /* set to False for absolute hints! */ fUseItemOffset = False; fHasContext = True; fRectangleContext = False; if (StrEquals(tok, "context")) { if (mi && mr) context_window = MR_WINDOW(mr); else if (tmp_win) { if (IS_ICONIFIED(tmp_win)) context_window = tmp_win->icon_pixmap_w; else context_window = tmp_win->frame; } else context_window = w; } else if (StrEquals(tok,"menu")) { if (mr) { context_window = MR_WINDOW(mr); pops->pos_hints.is_menu_relative = True; pops->pos_hints.menu_width = MR_WIDTH(mr); } } else if (StrEquals(tok,"item")) { if (mi && mr) { context_window = MR_WINDOW(mr); fUseItemOffset = True; pops->pos_hints.is_menu_relative = True; pops->pos_hints.menu_width = MR_WIDTH(mr); } } else if (StrEquals(tok,"icon")) { if (tmp_win && IS_ICONIFIED(tmp_win)) context_window = tmp_win->icon_pixmap_w; } else if (StrEquals(tok,"window")) { if (tmp_win && !IS_ICONIFIED(tmp_win)) context_window = tmp_win->frame; } else if (StrEquals(tok,"interior")) { if (tmp_win && !IS_ICONIFIED(tmp_win)) context_window = tmp_win->w; } else if (StrEquals(tok,"title")) { if (tmp_win) { if (IS_ICONIFIED(tmp_win)) context_window = tmp_win->icon_w; else context_window = tmp_win->title_w; } } else if (strncasecmp(tok,"button",6) == 0) { if (sscanf(&(tok[6]),"%d",&button) != 1 || tok[6] == '+' || tok[6] == '-' || button < 0 || button > 9) { fHasContext = False; } else if (tmp_win && !IS_ICONIFIED(tmp_win)) { button = BUTTON_INDEX(button); context_window = tmp_win->button_w[button]; } } else if (StrEquals(tok,"root")) { context_window = Scr.Root; pops->pos_hints.is_relative = False; } else if (StrEquals(tok,"xineramaroot")) { context_window = Scr.Root; pops->pos_hints.is_relative = False; fXineramaRoot = True; } else if (StrEquals(tok,"mouse")) { context_window = None; } else if (StrEquals(tok,"rectangle")) { int flags; int screen; int sx; int sy; int sw; int sh; /* parse the rectangle */ free(tok); naction = GetNextToken(naction, &tok); if (tok == NULL) { fvwm_msg(ERR,"get_menu_options","missing rectangle geometry"); if (!pops->pos_hints.has_screen_origin) { /* xinerama: emergency fallback */ pops->pos_hints.has_screen_origin = 1; pops->pos_hints.screen_origin_x = 0; pops->pos_hints.screen_origin_y = 0; } return action; } flags = FScreenParseGeometryWithScreen( tok, &x, &y, &width, &height, &screen); if ((flags & (XValue | YValue)) != (XValue | YValue)) { free(tok); fvwm_msg(ERR,"get_menu_options","invalid rectangle geometry"); if (!pops->pos_hints.has_screen_origin) { /* xinerama: emergency fallback */ pops->pos_hints.has_screen_origin = 1; pops->pos_hints.screen_origin_x = 0; pops->pos_hints.screen_origin_y = 0; } return action; } pops->pos_hints.has_screen_origin = 1; FScreenGetScrRect(NULL, screen, &sx, &sy, &sw, &sh); pops->pos_hints.screen_origin_x = sx; pops->pos_hints.screen_origin_y = sy; if (!(flags & WidthValue)) { width = 1; } if (!(flags & HeightValue)) { height = 1; } x += sx; y += sy; if (flags & XNegative) x = sx + sw - x - width; if (flags & YNegative) y = sy + sh - y - height; pops->pos_hints.is_relative = False; fRectangleContext = True; } else if (StrEquals(tok,"this")) { MenuRoot *dummy_mr; context_window = w; if (XFindContext(dpy, w, MenuContext, (caddr_t *)&dummy_mr) != XCNOENT) { if (mr) { pops->pos_hints.is_menu_relative = True; pops->pos_hints.menu_width = MR_WIDTH(mr); } } } else { /* no context string */ fHasContext = False; } if (tok) free(tok); if (fHasContext) taction = naction; else naction = action; if (fRectangleContext) { if (!pops->pos_hints.has_screen_origin) { /* xinerama: use global screen as reference */ pops->pos_hints.has_screen_origin = 1; pops->pos_hints.screen_origin_x = -1; pops->pos_hints.screen_origin_y = -1; } /* nothing else to do */ } else if (!fHasContext || !context_window || !XGetGeometry(dpy, context_window, &JunkRoot, &JunkX, &JunkY, &width, &height, &JunkBW, &JunkDepth) || !XTranslateCoordinates( dpy, context_window, Scr.Root, 0, 0, &x, &y, &JunkChild)) { /* no window or could not get geometry */ XQueryPointer(dpy,Scr.Root, &JunkRoot, &JunkChild, &x, &y, &JunkX, &JunkY, &JunkMask); width = height = 1; if (!pops->pos_hints.has_screen_origin) { /* xinerama: use screen with pinter as reference */ pops->pos_hints.has_screen_origin = 1; pops->pos_hints.screen_origin_x = x; pops->pos_hints.screen_origin_y = y; } } else { /* we have a context window */ if (fUseItemOffset) { y += MI_Y_OFFSET(mi); height = MI_HEIGHT(mi); } if (!pops->pos_hints.has_screen_origin) { pops->pos_hints.has_screen_origin = 1; if (fXineramaRoot) { /* use whole screen */ pops->pos_hints.screen_origin_x = -1; pops->pos_hints.screen_origin_y = -1; } else if (context_window == Scr.Root) { /* xinerama: use screen that contains the window center as reference */ if (!GetLocationFromEventOrQuery( dpy, context_window, e, &pops->pos_hints.screen_origin_x, &pops->pos_hints.screen_origin_y)) { pops->pos_hints.screen_origin_x = 0; pops->pos_hints.screen_origin_y = 0; } else { fscreen_scr_arg fscr; fscr.xypos.x = pops->pos_hints.screen_origin_x; fscr.xypos.y = pops->pos_hints.screen_origin_y; FScreenGetScrRect(&fscr, FSCREEN_XYPOS, &x, &y, &width, &height); } } else { /* xinerama: use screen that contains the window center as reference */ pops->pos_hints.screen_origin_x = JunkX + width / 2; pops->pos_hints.screen_origin_y = JunkY + height / 2; } } } /* parse position arguments */ taction = get_one_menu_position_argument( naction, x, width, &(pops->pos_hints.x), &(pops->pos_hints.x_offset), &(pops->pos_hints.x_factor), &(pops->pos_hints.context_x_factor), &pops->pos_hints.is_menu_relative); if (pops->pos_hints.is_menu_relative) { pops->pos_hints.x = x; if (pops->pos_hints.menu_width == 0 && mr) { pops->pos_hints.menu_width = MR_WIDTH(mr); } } naction = get_one_menu_position_argument( taction, y, height, &(pops->pos_hints.y), &dummy_int, &(pops->pos_hints.y_factor), &dummy_float, &dummy_flag); if (naction == taction) { /* argument is missing or invalid */ if (fHasContext) fvwm_msg(ERR,"get_menu_options","invalid position arguments"); naction = action; taction = action; break; } taction = naction; pops->flags.has_poshints = 1; if (fValidPosHints == True && pops->pos_hints.is_relative == True) { pops->pos_hints = last_saved_pos_hints.pos_hints; } /* we want to do this only once */ break; } /* while */ if (is_action_empty) { if (!pops->pos_hints.has_screen_origin) { pops->pos_hints.has_screen_origin = 1; if (!GetLocationFromEventOrQuery( dpy, None, e, &pops->pos_hints.screen_origin_x, &pops->pos_hints.screen_origin_y)) { pops->pos_hints.screen_origin_x = 0; pops->pos_hints.screen_origin_y = 0; } } } if (!pops->flags.has_poshints && fValidPosHints) { pops->flags.has_poshints = 1; pops->pos_hints = last_saved_pos_hints.pos_hints; pops->pos_hints.is_relative = False; } action = naction; /* to keep Purify silent */ pops->flags.do_select_in_place = 0; /* parse additional options */ while (naction && *naction) { naction = GetNextToken(action, &tok); if (!tok) break; if (StrEquals(tok, "WarpTitle")) { pops->flags.do_warp_title = 1; pops->flags.do_not_warp = 0; } else if (StrEquals(tok, "NoWarp")) { pops->flags.do_warp_title = 0; pops->flags.do_not_warp = 1; } else if (StrEquals(tok, "Fixed")) { pops->flags.is_fixed = 1; } else if (StrEquals(tok, "SelectInPlace")) { pops->flags.do_select_in_place = 1; } else if (StrEquals(tok, "SelectWarp")) { pops->flags.do_warp_on_select = 1; } else { free (tok); break; } action = naction; free (tok); } if (!pops->flags.do_select_in_place) { pops->flags.do_warp_on_select = 0; } return action; } /* Returns the menu options for the menu that a given menu item pops up */ static void get_popup_options( MenuParameters *pmp, MenuItem *mi, MenuOptions *pops) { if (!mi) return; pops->flags.has_poshints = 0; pops->pos_hints.has_screen_origin = True; pops->pos_hints.screen_origin_x = pmp->screen_origin_x; pops->pos_hints.screen_origin_y = pmp->screen_origin_y; /* just look past "Popup " in the action */ get_menu_options( SkipNTokens(MI_ACTION(mi), 2), MR_WINDOW(pmp->menu), NULL, NULL, pmp->menu, mi, pops); }