/* 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 <stdio.h>
#include <string.h>
#include <assert.h>
#include <X11/keysym.h>
#include <libs/fvwmlib.h>
#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 <name>" in the action */
get_menu_options(
SkipNTokens(MI_ACTION(mi), 2), MR_WINDOW(pmp->menu), NULL, NULL, pmp->menu,
mi, pops);
}
syntax highlighted by Code2HTML, v. 0.9.1