/* -*-c-*- */
/* 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
*/
/*
* 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
*/
/* IMPORTANT NOTE: Do *not* use any constant numbers in this file. All values
* have to be #defined in the section below or in defaults.h to ensure full
* control over the menus. */
/* ---------------------------- included header files ---------------------- */
#include "config.h"
#include <stdio.h>
#include <assert.h>
#include <X11/keysym.h>
#include "libs/ftime.h"
#include "libs/fvwmlib.h"
#include "libs/FScreen.h"
#include "libs/Grab.h"
#include "libs/Parse.h"
#include "libs/ColorUtils.h"
#include "libs/Picture.h"
#include "libs/PictureUtils.h"
#include "libs/Graphics.h"
#include "libs/PictureGraphics.h"
#include "libs/charmap.h"
#include "libs/wcontext.h"
#include "fvwm.h"
#include "externs.h"
#include "execcontext.h"
#include "events.h"
#include "eventhandler.h"
#include "eventmask.h"
#include "cursor.h"
#include "functions.h"
#include "commands.h"
#include "misc.h"
#include "screen.h"
#include "colormaps.h"
#include "geometry.h"
#include "move_resize.h"
#include "menudim.h"
#include "menuitem.h"
#include "menuroot.h"
#include "menustyle.h"
#include "bindings.h"
#include "menubindings.h"
#include "menugeometry.h"
#include "menuparameters.h"
#include "menus.h"
#include "libs/FGettext.h"
/* ---------------------------- local definitions -------------------------- */
/* used in float to int arithmetic */
#define ROUNDING_ERROR_TOLERANCE 0.005
/* ---------------------------- local macros ------------------------------- */
#define SCTX_SET_MI(ctx,item) ((ctx).type = SCTX_MENU_ITEM, \
(ctx).menu_item.menu_item = (item))
#define SCTX_GET_MI(ctx) ((ctx).type == SCTX_MENU_ITEM ? \
(ctx).menu_item.menu_item : NULL)
#define SCTX_SET_MR(ctx,root) ((ctx).type = SCTX_MENU_ROOT, \
(ctx).menu_root.menu_root = (root))
#define SCTX_GET_MR(ctx) ((ctx).type == SCTX_MENU_ROOT ? \
(ctx).menu_root.menu_root : NULL)
/* ---------------------------- imports ------------------------------------ */
/* This external is safe. It's written only during startup. */
extern XContext MenuContext;
/* ---------------------------- included code files ------------------------ */
/* ---------------------------- local types -------------------------------- */
/* 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;
unsigned do_warp_title : 1;
} flags;
struct MenuPosHints pos_hints;
} saved_pos_hints;
typedef struct MenuInfo
{
MenuRoot *all;
int n_destroyed_menus;
} MenuInfo;
typedef struct MenuSizingParameters
{
MenuRoot *menu;
/* number of item labels present in the item format */
int used_item_labels;
/* same for mini icons */
int used_mini_icons;
struct
{
int sidepic_width;
MenuItemPartSizesT i;
} max;
struct
{
unsigned is_popup_indicator_used : 1;
} flags;
} MenuSizingParameters;
typedef enum
{
SCTX_MENU_ROOT,
SCTX_MENU_ITEM
} string_context_type_t;
typedef union
{
string_context_type_t type;
struct
{
string_context_type_t type;
MenuRoot *menu_root;
} menu_root;
struct
{
string_context_type_t type;
MenuItem *menu_item;
} menu_item;
} string_context_t;
typedef struct
{
char delimiter;
Bool (*string_handler)(
char *string, char delimiter,
string_context_t *user_data);
} string_def_t;
/* ---------------------------- menu loop types ---------------------------- */
typedef enum
{
MENU_MLOOP_RET_NORMAL,
MENU_MLOOP_RET_LOOP,
MENU_MLOOP_RET_END
} mloop_ret_code_t;
typedef 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;
unsigned is_button_release : 1;
} mloop_flags_t;
typedef struct
{
MenuItem *mi;
MenuRoot *mrMi;
int x_offset;
int popdown_delay_10ms;
int popup_delay_10ms;
} mloop_evh_data_t;
typedef struct
{
MenuRoot *mrPopup;
MenuRoot *mrPopdown;
mloop_flags_t mif;
/* used to reduce network traffic with delayed popup/popdown */
MenuItem *mi_with_popup;
MenuItem *mi_wants_popup;
MenuItem *miRemovedSubmenu;
} mloop_evh_input_t;
/* values that are set once when the menu loop is entered */
typedef struct mloop_static_info_t
{
int x_init;
int y_init;
Time t0;
unsigned int event_mask;
} mloop_static_info_t;
/* ---------------------------- forward declarations ----------------------- */
/* ---------------------------- local variables ---------------------------- */
/* 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, False }
};
/* structures for menus */
static MenuInfo Menus;
/* ---------------------------- exported variables (globals) --------------- */
/* ---------------------------- local functions ---------------------------- */
static void __menu_execute_function(const exec_context_t **pexc, char *action)
{
const exec_context_t *exc;
exec_context_changes_t ecc;
int old_emf;
ecc.w.w = ((*pexc)->w.fw) ? FW_W((*pexc)->w.fw) : None;
exc = exc_clone_context(*pexc, &ecc, ECC_W);
old_emf = Scr.flags.is_executing_menu_function;
Scr.flags.is_executing_menu_function = 1;
execute_function(NULL, exc, action, FUNC_DONT_EXPAND_COMMAND);
Scr.flags.is_executing_menu_function = old_emf;
exc_destroy_context(exc);
/* See if the window has been deleted */
if (!check_if_fvwm_window_exists((*pexc)->w.fw))
{
ecc.w.fw = NULL;
ecc.w.w = None;
ecc.w.wcontext = 0;
exc = exc_clone_context(
*pexc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
exc_destroy_context(*pexc);
*pexc = exc;
}
return;
}
static Bool pointer_in_active_item_area(int x_offset, MenuRoot *mr)
{
float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
{
return False;
}
if (MST_USE_LEFT_SUBMENUS(mr))
{
return (x_offset <=
MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
MR_ITEM_WIDTH(mr) * ratio);
}
else
{
return (x_offset >=
MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
}
}
static Bool pointer_in_passive_item_area(int x_offset, MenuRoot *mr)
{
float ratio = (float)MST_ACTIVE_AREA_PERCENT(mr) / 100.0;
if (MST_ACTIVE_AREA_PERCENT(mr) >= 100)
{
return False;
}
if (MST_USE_LEFT_SUBMENUS(mr))
{
return (x_offset >=
MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) * ratio);
}
else
{
return (x_offset <=
MR_ITEM_X_OFFSET(mr) + MR_ITEM_WIDTH(mr) -
MR_ITEM_WIDTH(mr) * ratio);
}
}
/*
* warping functions
*/
static void warp_pointer_to_title(MenuRoot *mr)
{
FWarpPointer(
dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
menudim_middle_x_offset(&MR_DIM(mr)),
menuitem_middle_y_offset(MR_FIRST_ITEM(mr), MR_STYLE(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_SELECTABLE(mi) || MI_IS_TEAR_OFF_BAR(mi)))
{
/* skip separators, titles and tear off bars 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;
}
FWarpPointer(
dpy, 0, MR_WINDOW(mr), 0, 0, 0, 0,
menudim_middle_x_offset(&MR_DIM(mr)),
menuitem_middle_y_offset(mi, MR_STYLE(mr)));
return mi;
}
/*
* menu animation functions
*/
/* prepares the parameters to be passed to AnimatedMoveOfWindow
* mr - the menu instance that holds the menu item
* fw - the FvwmWindow structure to check against allowed functions */
static void get_menu_repaint_transparent_parameters(
MenuRepaintTransparentParameters *pmrtp, MenuRoot *mr, FvwmWindow *fw)
{
pmrtp->mr = mr;
pmrtp->fw = fw;
return;
}
/* Undo the animation of a menu */
static void animated_move_back(
MenuRoot *mr, Bool do_warp_pointer, FvwmWindow *fw)
{
MenuRepaintTransparentParameters mrtp;
int act_x;
int act_y;
if (MR_XANIMATION(mr) == 0)
{
return;
}
if (menu_get_geometry(
mr, &JunkRoot, &act_x, &act_y, &JunkWidth, &JunkHeight,
&JunkBW, &JunkDepth))
{
Bool transparent_bg = False;
/* move it back */
if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
{
transparent_bg = True;
get_menu_repaint_transparent_parameters(
&mrtp, mr, fw);
}
AnimatedMoveOfWindow(
MR_WINDOW(mr), act_x, act_y, act_x - MR_XANIMATION(mr),
act_y, do_warp_pointer, -1, NULL,
(transparent_bg)? &mrtp:NULL);
MR_XANIMATION(mr) = 0;
}
return;
}
/* move a menu or a tear-off menu preserving transparency.
* tear-off menus are moved with their frame coordinates. */
static void move_any_menu(
MenuRoot *mr, MenuParameters *pmp, int endX, int endY)
{
if (MR_IS_TEAR_OFF_MENU(mr))
{
float fFull = 1.0;
/* this moves the tearoff menu, updating of transparency
* will not be as good as if menu repaint parameters
* are used. */
AnimatedMoveFvwmWindow(
pmp->tear_off_root_menu_window,
FW_W_FRAME(pmp->tear_off_root_menu_window),
-1, -1, endX, endY, False, 0, &fFull);
}
else
{
int x;
int y;
int JunkDept;
menu_get_geometry(mr, &JunkRoot, &x, &y, &JunkWidth,
&JunkHeight, &JunkBW, &JunkDept);
if (x == endX && y == endY)
{
return;
}
if (ST_HAS_MENU_CSET(MR_STYLE(mr)) &&
CSET_IS_TRANSPARENT(ST_CSET_MENU(MR_STYLE(mr))))
{
MenuRepaintTransparentParameters mrtp;
get_menu_repaint_transparent_parameters(
&mrtp, mr, (*pmp->pexc)->w.fw);
update_transparent_menu_bg(
&mrtp, x, y, endX, endY, endX, endY);
XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
repaint_transparent_menu(
&mrtp, False, endX,endY, endX, endY, True);
}
else
{
XMoveWindow(dpy, MR_WINDOW(mr), endX, endY);
}
}
}
/* ---------------------------- submenu function --------------------------- */
/* 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 = menus_find_menu(menu_name);
return menu;
}
/* ---------------------------- item handling ------------------------------ */
/*
* 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(
MenuParameters *pmp,
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 FQueryPointer 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;
}
/* get the pointer position */
if (p_rx < 0)
{
if (!FQueryPointer(
dpy, Scr.Root, &JunkRoot, &Child,
&root_x, &root_y, &JunkX, &JunkY, &JunkMask))
{
/* pointer is on a different screen */
return NULL;
}
}
else
{
root_x = p_rx;
root_y = p_ry;
Child = p_child;
}
/* find out the menu the pointer is in */
if (pmp->tear_off_root_menu_window != NULL &&
Child == FW_W_FRAME(pmp->tear_off_root_menu_window))
{
/* we're in the top level torn off menu */
Child = FW_W(pmp->tear_off_root_menu_window);
}
if (XFindContext(dpy, Child, MenuContext, (caddr_t *)&mr) == XCNOENT)
{
return NULL;
}
/* get position in that child window */
if (!XTranslateCoordinates(
dpy, Scr.Root, MR_WINDOW(mr), root_x, root_y, &x, &y,
&JunkChild))
{
return NULL;
}
if (x < 0 || y < 0 || x >= MR_WIDTH(mr) || y >= MR_HEIGHT(mr))
{
return NULL;
}
if (pmr)
{
*pmr = mr;
}
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;
}
/* ---------------------------- keyboard shortcuts ------------------------- */
static Bool is_double_click(
Time t0, MenuItem *mi, MenuParameters *pmp, MenuReturn *pmret,
double_keypress *pdkp, Bool has_mouse_moved)
{
if ((*pmp->pexc)->x.elast->type == KeyPress)
{
return False;
}
if (fev_get_evtime() - 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;
}
/* ---------------------------- item label parsing ------------------------- */
/*
* 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;
char *s;
char *t;
/* Get start of string */
start = MI_LABEL(it)[column];
/* Scan whole string */
for (s = start; *s != '\0'; s++)
{
if (*s != '&')
{
continue;
}
if (s[1] != '&')
{
/* found a hotkey - only one hotkey per item */
break;
}
/* Just an escaped '&'; copy the string down over it */
for (t = s; *t != '\0'; t++)
{
t[0] = t[1];
}
}
if (*s != 0)
{
/* It's a hot key marker - work out the offset value */
MI_HOTKEY_COFFSET(it) = s - start;
MI_HOTKEY_COLUMN(it) = column;
MI_HAS_HOTKEY(it) = (s[1] != '\0');
MI_IS_HOTKEY_AUTOMATIC(it) = 0;
for ( ; *s != '\0'; s++)
{
/* Copy down.. */
s[0] = s[1];
}
}
return;
}
static void __copy_down(char *remove_from, char *remove_to)
{
char *t1;
char *t2;
for (t1 = remove_from, t2 = remove_to; *t2 != '\0'; t2++, t1++)
{
*t1 = *t2;
}
*t1 = '\0';
return;
}
static int __check_for_delimiter(char *s, const string_def_t *string_defs)
{
int type;
for (type = 0; string_defs[type].delimiter != '\0'; type++)
{
if (s[0] == string_defs[type].delimiter)
{
if (s[1] != string_defs[type].delimiter)
{
return type;
}
else
{
/* escaped delimiter, copy the
* string down over it */
__copy_down(s, s+1);
return -1;
}
}
}
return -1;
}
/* This scans for strings within delimiters and calls a callback based
* on the delimiter found for each found string */
static void scanForStrings(
char *instring, const string_def_t *string_defs,
string_context_t *context)
{
char *s;
int type;
char *string;
type = -1;
/* string is set whenever type >= 0, and unused otherwise
* set to NULL to supress compiler warning */
string = NULL;
for (s = instring; *s != '\0'; s++)
{
if (type < 0)
{
/* look for starting delimiters */
type = __check_for_delimiter(s, string_defs);
if (type >= 0)
{
/* start of a string */
string = s + 1;
}
}
else if (
s[0] == string_defs[type].delimiter &&
s[1] != string_defs[type].delimiter)
{
/* found ending delimiter */
Bool is_valid;
/* terminate the string pointer */
s[0] = '\0';
is_valid = string_defs[type].string_handler(
string, string_defs[type].delimiter, context);
/* restore the string */
s[0] = string_defs[type].delimiter;
if (is_valid)
{
/* the string was OK, remove it from
* instring */
__copy_down(string - 1, s + 1);
/* continue next iteration at the
* first character after the string */
s = string - 2;
}
type = -1;
}
else if (s[0] == string_defs[type].delimiter)
{
/* escaped delimiter, copy the string down over
* it */
__copy_down(s, s + 1);
}
}
}
/* Side picture support: this scans for a color int the menu name
for colorization */
static Bool __scan_for_color(
char *name, char type, string_context_t *context)
{
if (type != '^' || SCTX_GET_MR(*context) == NULL)
{
abort();
}
if (MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)))
{
return False;
}
MR_SIDECOLOR(SCTX_GET_MR(*context)) = GetColor(name);
MR_HAS_SIDECOLOR(SCTX_GET_MR(*context)) = True;
return True;
}
static Bool __scan_for_pixmap(
char *name, char type, string_context_t *context)
{
FvwmPicture *p;
FvwmPictureAttributes fpa;
int current_mini_icon;
/* check that more pictures are allowed before trying to load the
* picture */
current_mini_icon = -999999999;
switch (type)
{
case '@':
if (SCTX_GET_MR(*context) == NULL)
{
abort();
}
if (MR_SIDEPIC(SCTX_GET_MR(*context)))
{
return False;
}
break;
case '*':
/* menu item picture, requires menu item */
if (SCTX_GET_MI(*context) == NULL)
{
abort();
}
if (MI_PICTURE(SCTX_GET_MI(*context)))
{
return False;
}
break;
case '%':
/* mini icon - look for next free spot */
if (SCTX_GET_MI(*context) == NULL)
{
abort();
}
current_mini_icon = 0;
while (current_mini_icon < MAX_MENU_ITEM_MINI_ICONS)
{
if (
MI_MINI_ICON(SCTX_GET_MI(*context))
[current_mini_icon])
{
current_mini_icon++;
}
else
{
break;
}
}
if (current_mini_icon == MAX_MENU_ITEM_MINI_ICONS)
{
return False;
}
break;
default:
abort();
}
fpa.mask = 0;
p = PCacheFvwmPicture(
dpy, Scr.NoFocusWin, NULL, name, fpa);
if (!p)
{
fvwm_msg(WARN, "scanForPixmap",
"Couldn't load image from %s", name);
/* return true to make missing pictures not appear in the
* label/name */
return True;
}
switch (type)
{
case '@':
MR_SIDEPIC(SCTX_GET_MR(*context)) = p;
break;
case '*':
MI_PICTURE(SCTX_GET_MI(*context)) = p;
MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
break;
case '%':
MI_MINI_ICON(SCTX_GET_MI(*context))[current_mini_icon] = p;
MI_HAS_PICTURE(SCTX_GET_MI(*context)) = True;
break;
}
return True;
}
/* ---------------------------- item list handling ------------------------- */
static void unlink_item_from_menu(
MenuRoot *mr, MenuItem *mi)
{
MenuItem *next;
MenuItem *prev;
next = MI_NEXT_ITEM(mi);
prev = MI_PREV_ITEM(mi);
if (next != NULL)
{
MI_PREV_ITEM(next) = prev;
}
else
{
MR_LAST_ITEM(mr) = prev;
}
if (prev != NULL)
{
MI_NEXT_ITEM(prev) = next;
}
else
{
MR_FIRST_ITEM(mr) = next;
}
MI_NEXT_ITEM(mi) = NULL;
MI_PREV_ITEM(mi) = NULL;
MR_ITEMS(mr)--;
return;
}
/* Add the given menu item to the menu. If the first item of the menu is a
* title, and the do_replace_title flag is True, the old title is deleted and
* replaced by the new item. Otherwise the item is appended at the end of the
* menu. */
static void append_item_to_menu(
MenuRoot *mr, MenuItem *mi, Bool do_replace_title)
{
if (MR_FIRST_ITEM(mr) == NULL)
{
MR_FIRST_ITEM(mr) = mi;
MR_LAST_ITEM(mr) = mi;
MI_NEXT_ITEM(mi) = NULL;
MI_PREV_ITEM(mi) = NULL;
}
else if (do_replace_title)
{
if (MI_IS_TITLE(MR_FIRST_ITEM(mr)))
{
if (MR_FIRST_ITEM(mr) == MR_LAST_ITEM(mr))
{
MR_LAST_ITEM(mr) = mi;
}
if (MI_NEXT_ITEM(MR_FIRST_ITEM(mr)) != NULL)
{
MI_PREV_ITEM(MI_NEXT_ITEM(
MR_FIRST_ITEM(mr))) = mi;
}
MI_NEXT_ITEM(mi) = MI_NEXT_ITEM(MR_FIRST_ITEM(mr));
menuitem_free(MR_FIRST_ITEM(mr));
}
else
{
MI_PREV_ITEM(MR_FIRST_ITEM(mr)) = mi;
MI_NEXT_ITEM(mi) = MR_FIRST_ITEM(mr);
}
MI_PREV_ITEM(mi) = NULL;
MR_FIRST_ITEM(mr) = mi;
}
else
{
MI_NEXT_ITEM(MR_LAST_ITEM(mr)) = mi;
MI_PREV_ITEM(mi) = MR_LAST_ITEM(mr);
MR_LAST_ITEM(mr) = mi;
}
return;
}
static void clone_menu_item_list(
MenuRoot *dest_mr, MenuRoot *src_mr)
{
MenuItem *mi;
MenuItem *cloned_mi;
MenuRoot *mr;
MR_FIRST_ITEM(dest_mr) = NULL;
MR_LAST_ITEM(dest_mr) = NULL;
/* traverse the menu and all its continuations */
for (mr = src_mr; mr != NULL; mr = MR_CONTINUATION_MENU(mr))
{
/* duplicate all items in the current menu */
for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
{
if (MI_IS_CONTINUATION(mi))
{
/* skip this item */
continue;
}
cloned_mi = menuitem_clone(mi);
append_item_to_menu(dest_mr, cloned_mi, False);
}
}
return;
}
/* ---------------------------- MenuRoot maintenance functions ------------- */
/* 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;
MenuItemPartSizesT mipst;
int i;
Bool do_reverse_icon_order =
(MST_USE_LEFT_SUBMENUS(msp->menu)) ? True : False;
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_TITLE(mi))
{
menuitem_get_size(
mi, &mipst, MST_PTITLEFONT(msp->menu),
do_reverse_icon_order);
}
else
{
menuitem_get_size(
mi, &mipst, MST_PSTDFONT(msp->menu),
do_reverse_icon_order);
}
/* adjust maximums */
if (msp->max.i.triangle_width < mipst.triangle_width)
{
msp->max.i.triangle_width = mipst.triangle_width;
}
if (msp->max.i.title_width < mipst.title_width)
{
msp->max.i.title_width = mipst.title_width;
}
for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
{
if (msp->max.i.label_width[i] < mipst.label_width[i])
{
msp->max.i.label_width[i] =
mipst.label_width[i];
}
}
if (msp->max.i.picture_width < mipst.picture_width)
{
msp->max.i.picture_width = mipst.picture_width;
}
for (i = 0; i < MAX_MENU_ITEM_MINI_ICONS; i++)
{
if (msp->max.i.icon_width[i] < mipst.icon_width[i])
{
msp->max.i.icon_width[i] = mipst.icon_width[i];
}
}
}
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;
int label_offset[MAX_MENU_ITEM_LABELS];
char lcr_column[MAX_MENU_ITEM_LABELS];
int i;
int d;
int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
int *item_order[
MAX_MENU_ITEM_LABELS + MAX_MENU_ITEM_MINI_ICONS +
1 /* triangle */ + 2 /* relief markers */];
int used_objects = 0;
int left_objects = 0;
int right_objects = 0;
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;
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. */
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)
{
break;
}
if (
msp->max.i.label_width[columns_placed]
<= 0)
{
columns_placed++;
break;
}
lcr_column[columns_placed] = *format;
x += gap_left;
label_offset[columns_placed] = x;
x += msp->max.i.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)
{
break;
}
sidepic_placed = True;
if (msp->max.sidepic_width <= 0)
{
break;
}
x += gap_left;
MR_SIDEPIC_X_OFFSET(msp->menu) = x;
sidepic_is_left = first;
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)
{
break;
}
if (msp->max.i.icon_width[icons_placed] > 0)
{
x += gap_left;
MR_ICON_X_OFFSET(msp->menu)
[icons_placed] = x;
x += msp->max.i.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)
{
break;
}
triangle_placed = True;
if (msp->max.i.triangle_width > 0)
{
x += gap_left;
MR_TRIANGLE_X_OFFSET(
msp->menu) = x;
MR_IS_LEFT_TRIANGLE(msp->menu) =
(*format == '<');
x += msp->max.i.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 * FlocaleTextWidth(
MST_PSTDFONT(
msp->menu), " ", 1);
break;
case ' ':
/* Advance the x position. */
x += FlocaleTextWidth(
MST_PSTDFONT(msp->menu), format,
1);
break;
default:
/* Ignore unknown characters. */
break;
} /* switch (*format) */
break;
case '\t':
x += MENU_TAB_WIDTH * FlocaleTextWidth(
MST_PSTDFONT(msp->menu), " ", 1);
break;
case ' ':
/* Advance the x position. */
x += FlocaleTextWidth(
MST_PSTDFONT(msp->menu), 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.i.title_width, msp->max.i.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)
{
continue;
}
if (i >= used_objects - right_objects)
{
/* Right aligned item. */
*(item_order[i]) += d;
}
else
{
/* Neither left nor right aligned item.
* Divide the overhead gap evenly
* between the items. */
*(item_order[i]) += d * (m + i) / n;
}
}
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] == NULL)
{
continue;
}
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.i.label_width[i] -
MI_LABEL_OFFSET(mi)[i]) / 2;
break;
case 'r':
MI_LABEL_OFFSET(mi)[i] =
label_offset[i] +
msp->max.i.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] =
menudim_middle_x_offset(
&MR_DIM(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;
int relief_thickness = MST_RELIEF_THICKNESS(msp->menu);
int simple_entry_height;
int i;
Bool has_continuation_menu = False;
MR_ITEM_TEXT_Y_OFFSET(msp->menu) =
MST_PSTDFONT(msp->menu)->ascent + 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_HEIGHT(mi) = MST_PTITLEFONT(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 if (MI_IS_TEAR_OFF_BAR(mi))
{
/* Tear off bar */
MI_HEIGHT(mi) = relief_thickness +
MENU_TEAR_OFF_BAR_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, gettext("More&..."), tempname,
False /* no pixmap scan */, False, True);
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;
}
/*
*
* 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));
menuitem_free(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;
}
/*
*
* Creates the window for the menu.
*
*/
static void make_menu_window(MenuRoot *mr, Bool is_tear_off)
{
unsigned long valuemask;
XSetWindowAttributes attributes;
int w;
int h;
unsigned int evmask;
w = MR_WIDTH(mr);
if (w == 0)
{
w = 1;
}
h = MR_HEIGHT(mr);
if (h == 0)
{
h = 1;
}
attributes.background_pixel = (MST_HAS_MENU_CSET(mr)) ?
Colorset[MST_CSET_MENU(mr)].bg : MST_MENU_COLORS(mr).back;
if (MR_WINDOW(mr) != None)
{
/* just resize the existing window */
XResizeWindow(dpy, MR_WINDOW(mr), w, h);
/* and change the background color */
valuemask = CWBackPixel | CWCursor;
XChangeWindowAttributes(
dpy, MR_WINDOW(mr), valuemask, &attributes);
}
else
{
/* create a new window */
valuemask = CWBackPixel | CWEventMask | CWCursor | CWColormap
| CWBorderPixel | CWSaveUnder;
attributes.border_pixel = 0;
attributes.colormap = Pcmap;
evmask = XEVMASK_MENUW;
attributes.event_mask = 0;
attributes.cursor = Scr.FvwmCursors[CRS_MENU];
attributes.save_under = True;
/* Create a display used to create the window. Can't use the
* normal display because 'xkill' would kill the window
* manager if used on a tear off menu. The display can't be
* deleted right now because that would either destroy the new
* window or leave it as an orphan if fvwm dies or is
* restarted. */
if (is_tear_off)
{
MR_CREATE_DPY(mr) = XOpenDisplay(display_name);
if (MR_CREATE_DPY(mr) == NULL)
{
/* Doh. Use the standard display instead. */
MR_CREATE_DPY(mr) = dpy;
}
}
else
{
MR_CREATE_DPY(mr) = dpy;
}
MR_WINDOW(mr) = XCreateWindow(
MR_CREATE_DPY(mr), Scr.Root, 0, 0, w, h,
0, Pdepth, InputOutput, Pvisual, valuemask,
&attributes);
if (MR_CREATE_DPY(mr) != dpy)
{
/* We *must* synchronize the display here. Otherwise
* the request will never be processed. */
XSync(MR_CREATE_DPY(mr), 1);
}
if (MR_WINDOW(mr) != None)
{
/* select events for the window from the standard
* display */
XSelectInput(dpy, MR_WINDOW(mr), evmask);
}
XSaveContext(dpy, MR_WINDOW(mr), MenuContext,(caddr_t)mr);
}
return;
}
/*
*
* Generates the window for a menu
*
*/
static void make_menu(MenuRoot *mr, Bool is_tear_off)
{
MenuSizingParameters msp;
Bool has_continuation_menu = False;
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_USED_MINI_ICONS(mr) = msp.used_mini_icons;
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, is_tear_off);
MR_IS_UPDATED(mr) = 0;
return;
}
/* 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) = sw;
MR_SCREEN_HEIGHT(mr) = 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, False);
}
return;
}
/*
*
* Procedure:
* copy_menu_root - creates a new instance of an existing menu
*
* Returned Value:
* (MenuRoot *)
*
* Inputs:
* mr - 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;
}
/*
*
* Procedure:
* clone_menu - duplicates an existing menu in newly allocated
memory. The new menu is independent of the original.
*
* Returned Value:
* (MenuRoot *)
*
* Inputs:
* mr - the MenuRoot structure of the existing menu
*
*/
static void clone_menu_root_static(
MenuRoot *dest_mr, MenuRoot *src_mr)
{
dest_mr->s = (MenuRootStatic *)safemalloc(sizeof(MenuRootStatic));
/* copy everything */
memcpy(dest_mr->s, src_mr->s, sizeof(MenuRootStatic));
/* special treatment for a few parts */
if (MR_NAME(src_mr) != NULL)
{
MR_NAME(dest_mr) = safestrdup(MR_NAME(src_mr));
}
MR_COPIES(dest_mr) = 1;
MR_MAPPED_COPIES(dest_mr) = 0;
MR_POPUP_ACTION(dest_mr) = NULL;
MR_POPDOWN_ACTION(dest_mr) = NULL;
if (MR_MISSING_SUBMENU_FUNC(src_mr))
{
MR_MISSING_SUBMENU_FUNC(dest_mr) =
safestrdup(MR_MISSING_SUBMENU_FUNC(src_mr));
}
if (MR_HAS_SIDECOLOR(src_mr))
{
MR_SIDECOLOR(dest_mr) =
fvwmlib_clone_color(MR_SIDECOLOR(src_mr));
}
MR_SIDEPIC(dest_mr) = PCloneFvwmPicture(MR_SIDEPIC(src_mr));
clone_menu_item_list(dest_mr, src_mr);
return;
}
static MenuRoot *clone_menu(MenuRoot *mr)
{
MenuRoot *new_mr;
new_mr = (MenuRoot *)safemalloc(sizeof(MenuRoot));
new_mr->d = (MenuRootDynamic *)safemalloc(sizeof(MenuRootDynamic));
memset(new_mr->d, 0, sizeof(MenuRootDynamic));
clone_menu_root_static(new_mr, mr);
return new_mr;
}
/* ---------------------------- 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(
struct 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);
return;
}
/*
* 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)
{
naction = action;
break;
}
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;
}
}
*pFinalX += float_to_int_with_tolerance(x_add);
free(orgtoken);
return naction;
}
/* 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);
return;
}
/* ---------------------------- menu painting functions --------------------- */
static void clear_expose_menu_area(Window win, XEvent *e)
{
if (e == NULL)
{
XClearWindow(dpy, win);
}
else
{
XClearArea(
dpy, win, e->xexpose.x, e->xexpose.y, e->xexpose.width,
e->xexpose.height, False);
}
return;
}
/*
*
* Draws a picture on the left side of the menu
* What about a SidePic Colorset ? (olicha 2002-08-21)
*
*/
static void paint_side_pic(MenuRoot *mr, XEvent *pevent)
{
GC gc;
FvwmPicture *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)
{
/* ? */
gc = SHADOW_GC(MST_MENU_INACTIVE_GCS(mr));
}
else
{
gc = FORE_GC(MST_MENU_INACTIVE_GCS(mr));
}
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 (pevent != NULL && pevent->type == Expose)
{
if (
pevent->xexpose.x + pevent->xexpose.width <
MR_SIDEPIC_X_OFFSET(mr) ||
pevent->xexpose.x >=
MR_SIDEPIC_X_OFFSET(mr) + sidePic->width)
{
/* out of x-range for side bar */
return;
}
if (
pevent->xexpose.y + pevent->xexpose.height < bw ||
pevent->xexpose.y >= bw + MR_HEIGHT(mr))
{
/* in the border */
return;
}
if (
!(MR_HAS_SIDECOLOR(mr) || MST_HAS_SIDE_COLOR(mr)) &&
pevent->xexpose.y + pevent->xexpose.height < yt)
{
/* outside picture and no background */
return;
}
}
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);
}
else if (sidePic->alpha != None)
{
XClearArea(
dpy, MR_WINDOW(mr),
MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
}
PGraphicsRenderPicture(
dpy, MR_WINDOW(mr), sidePic, 0, MR_WINDOW(mr),
gc, Scr.MonoGC, Scr.AlphaGC,
0, ys, sidePic->width, h,
MR_SIDEPIC_X_OFFSET(mr), yt, sidePic->width, h, False);
return;
}
static Bool paint_menu_gradient_background(
MenuRoot *mr, XEvent *pevent)
{
MenuStyle *ms = MR_STYLE(mr);
int bw = MST_BORDER_WIDTH(mr);
XRectangle bounds;
Pixmap pmap;
GC pmapgc;
XGCValues gcv;
unsigned long gcm = GCLineWidth;
int switcher = -1;
Bool do_clear = False;
gcv.line_width = 1;
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. (if no dither) */
if (!ST_FACE(ms).u.grad.do_dither)
{
switcher = ST_FACE(ms).gradient_type;
}
switch (switcher)
{
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++)
{
int x;
x = i * bounds.width /
ST_FACE(ms).u.grad.npixels;
XSetForeground(
dpy, pmapgc,
ST_FACE(ms).u.grad.xcs[i].pixel);
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 int best_tile_width = 0;
int junk;
if (best_tile_width == 0)
{
int tw = DEFAULT_MENU_GRADIENT_PIXMAP_THICKNESS;
if (!XQueryBestTile(
dpy, Scr.screen, tw, tw,
(unsigned int*)&best_tile_width,
(unsigned int*)&junk))
{
/* call failed, use default and risk a
* screwed up tile */
best_tile_width = tw;
}
}
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++)
{
int y;
y = i * bounds.height /
ST_FACE(ms).u.grad.npixels;
XSetForeground(
dpy, pmapgc,
ST_FACE(ms).u.grad.xcs[i].pixel);
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;
FvwmPicture *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.xcs[cindex].pixel);
}
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)
{
int g_width;
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,
ST_FACE(ms).u.grad.do_dither, &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.xcs,
ST_FACE(ms).u.grad.do_dither,
&(MR_STORED_PIXELS(mr).d_pixels),
&(MR_STORED_PIXELS(mr).d_npixels),
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;
}
return do_clear;
}
static Bool paint_menu_pixmap_background(
MenuRoot *mr, XEvent *pevent)
{
MenuStyle *ms = MR_STYLE(mr);
int width, height, x, y;
int bw = MST_BORDER_WIDTH(mr);
FvwmPicture *p;
p = ST_FACE(ms).u.p;
width = MR_WIDTH(mr) - 2 * bw;
height = MR_HEIGHT(mr) - 2 * bw;
y = (int)(height - p->height) / 2;
x = (int)(width - p->width) / 2;
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);
return False;
}
/*
*
* Procedure:
* get_menu_paint_item_parameters - prepares the parameters to be
* passed to menuitem_paint().
*
* 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 get_menu_paint_item_parameters(
MenuPaintItemParameters *mpip, MenuRoot *mr, MenuItem *mi,
FvwmWindow *fw, XEvent *pevent, Bool do_redraw_menu_border)
{
mpip->ms = MR_STYLE(mr);
mpip->w = MR_WINDOW(mr);
mpip->selected_item = MR_SELECTED_ITEM(mr);
mpip->dim = &MR_DIM(mr);
mpip->fw = fw;
mpip->used_mini_icons = MR_USED_MINI_ICONS(mr);
mpip->cb_mr = mr;
mpip->cb_reset_bg = paint_menu_gradient_background;
mpip->flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
mpip->flags.is_left_triangle = MR_IS_LEFT_TRIANGLE(mr);
mpip->ev = pevent;
return;
}
/*
*
* Procedure:
* paint_menu - draws the entire menu
*
*/
static void paint_menu(
MenuRoot *mr, XEvent *pevent, FvwmWindow *fw)
{
MenuItem *mi;
MenuStyle *ms = MR_STYLE(mr);
int bw = MST_BORDER_WIDTH(mr);
XGCValues gcv;
int relief_thickness = ST_RELIEF_THICKNESS(MR_STYLE(mr));
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) ?
SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
{
return;
}
}
MR_IS_PAINTED(mr) = 1;
/* paint the menu background */
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, FORE_GC(ST_MENU_INACTIVE_GCS(ms)),
True);
MR_IS_BACKGROUND_SET(mr) = True;
}
}
else if (ms)
{
int type;
Bool do_clear = False;
type = ST_FACE(ms).type;
switch(type)
{
case SolidMenu:
XSetWindowBackground(
dpy, MR_WINDOW(mr), MST_FACE(mr).u.back);
do_clear = True;
break;
case GradientMenu:
do_clear = paint_menu_gradient_background(mr, pevent);
break;
case PixmapMenu:
do_clear = paint_menu_pixmap_background(mr, pevent);
break;
case TiledPixmapMenu:
XSetWindowBackgroundPixmap(
dpy, MR_WINDOW(mr), ST_FACE(ms).u.p->picture);
do_clear = True;
break;
} /* switch(type) */
if (do_clear == True)
{
clear_expose_menu_area(MR_WINDOW(mr), pevent);
}
} /* if (ms) */
/* draw the relief */
RelieveRectangle(dpy, MR_WINDOW(mr), 0, 0, MR_WIDTH(mr) - 1,
MR_HEIGHT(mr) - 1, (Pdepth < 2) ?
SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)) :
HILIGHT_GC(MST_MENU_INACTIVE_GCS(mr)),
SHADOW_GC(MST_MENU_INACTIVE_GCS(mr)), bw);
/* paint the menu items */
for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
{
int do_draw = 0;
/* be smart about handling the expose, redraw only the entries
* that we need to */
if (pevent == NULL)
{
do_draw = True;
}
else
{
register int b_offset;
b_offset = MI_Y_OFFSET(mi) + MI_HEIGHT(mi);
if (MR_SELECTED_ITEM(mr) == mi)
{
b_offset += relief_thickness;
}
if (pevent->xexpose.y < b_offset &&
(pevent->xexpose.y + pevent->xexpose.height) >
MI_Y_OFFSET(mi))
{
do_draw = True;
}
}
if (do_draw)
{
MenuPaintItemParameters mpip;
get_menu_paint_item_parameters(
&mpip, mr, NULL, fw, pevent, True);
mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
menuitem_paint(mi, &mpip);
}
}
paint_side_pic(mr, pevent);
XFlush(dpy);
return;
}
/* 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;
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,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
True);
XSync(dpy, 0);
while (FCheckTypedEvent(dpy, NoExpose, &e))
{
/* nothing to do here */
}
XCopyArea(
dpy, MR_WINDOW(mr),
MR_STORED_ITEM(mr).stored,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
MST_BORDER_WIDTH(mr), iy, mw, ih, 0,
0);
XSync(dpy, 0);
if (FCheckTypedEvent(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,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
False);
}
else if (select == False &&
MR_STORED_ITEM(mr).width != 0)
{
/* ungrab image */
XCopyArea(
dpy, MR_STORED_ITEM(mr).stored,
MR_WINDOW(mr),
FORE_GC(MST_MENU_INACTIVE_GCS(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;
FvwmPicture *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;
if (MR_IS_PAINTED(mr))
{
MenuPaintItemParameters mpip;
get_menu_paint_item_parameters(&mpip, mr, mi, fw, NULL, False);
menuitem_paint(mi, &mpip);
}
return;
}
/* ---------------------------- popping menu up or down -------------------- */
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 (!menu_get_geometry(
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;
}
}
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;
int prior_width, prior_height;
if (mr == NULL)
{
return 0;
}
if (!menu_get_geometry(
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, const exec_context_t **pexc, int x, int y,
Bool prefer_left_submenu, Bool do_warp_to_item, MenuOptions *pops,
Bool *ret_overlap, 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;
int prev_width;
int prev_height;
unsigned int event_mask;
int scr_x, scr_y;
int scr_w, scr_h;
mr = *pmenu;
if (!mr ||
(MR_MAPPED_COPIES(mr) > 0 && MR_COPIES(mr) >= MAX_MENU_COPIES))
{
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 is_busy_grabbed = False;
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;
if (Scr.BusyCursor & BUSY_DYNAMICMENU)
{
is_busy_grabbed = GrabEm(CRS_WAIT, GRAB_BUSYMENU);
}
/* Execute the action */
__menu_execute_function(pmp->pexc, MR_POPUP_ACTION(mr));
if (is_busy_grabbed)
{
UngrabEm(GRAB_BUSYMENU);
}
/* restore the stuff we saved */
last_saved_pos_hints = pos_hints;
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. menus_find_menu() always
* returns 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 = menus_find_menu(menu_name);
if (menu_name)
{
free(menu_name);
}
mr = *pmenu;
}
}
if (mr)
{
update_menu(mr, pmp);
}
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. */
return False;
}
fw = (*pexc)->w.fw;
if (fw && !check_if_fvwm_window_exists(fw))
{
fw = NULL;
}
context = (*pexc)->w.wcontext;
/*
* 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, False);
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 cx;
int cy;
int gx;
int gy;
Bool has_context;
rectangle button_g;
has_context = get_title_button_geometry(
fw, &button_g, context);
if (has_context)
{
fscreen_scr_arg fscr;
get_title_gravity_factors(fw, &gx, &gy);
cx = button_g.x + button_g.width / 2;
cy = button_g.y + button_g.height / 2;
if (gx != 0)
{
x = button_g.x +
(gx == 1) * button_g.width -
(gx == -1) * MR_WIDTH(mr);
}
if (gy != 0)
{
y = button_g.y +
(gy == 1) * button_g.height -
(gy == -1) * MR_HEIGHT(mr);
}
if (gx == 0 && x < button_g.x)
{
x = button_g.x;
}
else if (gx == 0 && x + MR_WIDTH(mr) >=
button_g.x + button_g.width)
{
x = button_g.x + button_g.width - MR_WIDTH(mr);
}
if (gy == 0 && y < button_g.y)
{
y = button_g.y;
}
else if (gy == 0 && y + MR_HEIGHT(mr) >=
button_g.y + button_g.height)
{
y = button_g.y + button_g.height -
MR_HEIGHT(mr);
}
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 (FQueryPointer(
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 ||
!menu_get_geometry(
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);
MenuRepaintTransparentParameters mrtp;
Bool transparent_bg = False;
/* 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;
Window w;
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;
if (ST_HAS_MENU_CSET(MR_STYLE(parent_menu)) &&
CSET_IS_TRANSPARENT(
ST_CSET_MENU(
MR_STYLE(parent_menu))))
{
transparent_bg = True;
get_menu_repaint_transparent_parameters(
&mrtp, parent_menu, fw);
}
if (MR_IS_TEAR_OFF_MENU(parent_menu))
{
int cx;
int cy;
w = FW_W_FRAME(
pmp->tear_off_root_menu_window
);
if (XGetGeometry(
dpy, w, &JunkRoot, &cx,
&cy,
(unsigned int*)&JunkWidth,
(unsigned int*)&JunkHeight,
(unsigned int*)&JunkBW,
(unsigned int*)&JunkDepth))
{
end_x += (cx - prev_x );
prev_x = cx;
prev_y = cy;
}
}
else
{
w = MR_WINDOW(parent_menu);
}
AnimatedMoveOfWindow(
w, prev_x, prev_y, end_x, prev_y, True,
-1, NULL,
(transparent_bg)? &mrtp: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 = PARENT_MENU_FORCE_VISIBLE_WIDTH +
prev_x;
}
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) */
/*
* Make sure we have the correct events selected
*/
if (pmp->tear_off_root_menu_window == NULL)
{
/* normal menus and sub menus */
event_mask = XEVMASK_MENUW;
}
else if (parent_menu == NULL)
{
/* tear off menu needs more events */
event_mask = XEVMASK_TEAR_OFF_MENUW;
}
else
{
/* sub menus of tear off menus need LeaveNotify */
event_mask = XEVMASK_TEAR_OFF_SUBMENUW;
}
/*
* Pop up the menu
*/
XMoveWindow(dpy, MR_WINDOW(mr), x, y);
XSelectInput(dpy, MR_WINDOW(mr), event_mask);
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 && parent_menu != NULL)
{
MenuItem *mi;
mi = find_entry(pmp, 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 (pops->flags.has_poshints &&
!last_saved_pos_hints.flags.do_ignore_pos_hints &&
pops->flags.do_warp_title)
{
do_warp_to_title = True;
}
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;
}
/*
*
* 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 ((mi = MR_SELECTED_ITEM(*pmr)) != NULL)
{
select_menu_item(*pmr, mi, False, (*pmp->pexc)->w.fw);
}
if (MR_STORED_PIXELS(*pmr).d_pixels != NULL)
{
PictureFreeColors(
dpy, Pcmap, MR_STORED_PIXELS(*pmr).d_pixels,
MR_STORED_PIXELS(*pmr).d_npixels, 0, False);
free(MR_STORED_PIXELS(*pmr).d_pixels);
MR_STORED_PIXELS(*pmr).d_pixels = NULL;
}
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;
/* save variables that we still need but that may be
* overwritten */
pos_hints = last_saved_pos_hints;
/* Execute the action */
__menu_execute_function(pmp->pexc, MR_POPDOWN_ACTION(*pmr));
/* restore the stuff we saved */
last_saved_pos_hints = pos_hints;
}
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;
int mr_width;
int mr_height;
int parent_x;
int parent_y;
int parent_width;
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 (!menu_get_geometry(
*pmr, &JunkRoot, &mr_x, &mr_y, &mr_width,
&mr_height, &JunkBW, &JunkDepth) ||
!menu_get_geometry(
parent, &JunkRoot, &parent_x, &parent_y,
&parent_width, &parent_height, &JunkBW,
&JunkDepth))
{
pop_menu_down(pmr, pmp);
paint_menu(parent, NULL, (*pmp->pexc)->w.fw);
}
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->pexc)->w.fw);
}
}
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;
}
/* ---------------------------- menu main loop ------------------------------ */
static void __mloop_init(
MenuParameters *pmp, MenuReturn *pmret,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
MenuOptions *pops)
{
memset(in, 0, sizeof(*in));
in->mif.do_force_reposition = 1;
memset(med, 0, sizeof(*med));
msi->t0 = fev_get_evtime();
pmret->rc = MENU_NOP;
memset(pops, 0, sizeof(*pops));
/* remember where the pointer was so we can tell if it has moved */
if (FQueryPointer(
dpy, Scr.Root, &JunkRoot, &JunkChild, &(msi->x_init),
&(msi->y_init), &JunkX, &JunkY, &JunkMask) == False)
{
/* pointer is on a different screen */
msi->x_init = 0;
msi->y_init = 0;
}
/* get the event mask right */
msi->event_mask = (pmp->tear_off_root_menu_window == NULL) ?
XEVMASK_MENU : XEVMASK_TEAR_OFF_MENU;
return;
}
static void __mloop_get_event_timeout_loop(
MenuParameters *pmp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
{
XEvent e = *(*pmp->pexc)->x.elast;
while (!FPending(dpy) || !FCheckMaskEvent(dpy, msi->event_mask, &e))
{
Bool is_popup_timed_out =
(MST_POPUP_DELAY(pmp->menu) > 0 &&
med->popup_delay_10ms++ >=
MST_POPUP_DELAY(pmp->menu) + 1);
Bool is_popdown_timed_out =
(MST_POPDOWN_DELAY(pmp->menu) > 0 && in->mrPopdown &&
med->popdown_delay_10ms++ >=
MST_POPDOWN_DELAY(pmp->menu) + 1);
Bool do_fake_motion = False;
if (is_popup_timed_out)
{
med->popup_delay_10ms = MST_POPUP_DELAY(pmp->menu);
}
if (is_popdown_timed_out)
{
med->popdown_delay_10ms = MST_POPDOWN_DELAY(pmp->menu);
}
if (
in->mif.do_force_popup ||
(is_popup_timed_out &&
(is_popdown_timed_out ||
in->mif.is_item_entered_by_key_press ||
!in->mrPopdown ||
MST_DO_POPDOWN_IMMEDIATELY(pmp->menu))))
{
in->mif.do_popup_now = True;
in->mif.do_force_popup = False;
do_fake_motion = True;
in->mif.is_popped_up_by_timeout = True;
is_popdown_timed_out = True;
}
if ((in->mrPopdown || in->mrPopup) &&
(is_popdown_timed_out ||
MST_DO_POPDOWN_IMMEDIATELY(pmp->menu)))
{
MenuRoot *m = (in->mrPopdown) ?
in->mrPopdown : in->mrPopup;
if (!m || med->mi != MR_PARENT_ITEM(m))
{
in->mif.do_popdown_now = True;
do_fake_motion = True;
if (MST_DO_POPUP_IMMEDIATELY(pmp->menu))
{
in->mif.do_popup_now = True;
in->mif.is_popped_up_by_timeout = True;
}
else if (
!MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
in->mif.is_pointer_in_active_item_area)
{
in->mif.do_popup_now = True;
in->mif.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) &&
med->popup_delay_10ms ==
med->popdown_delay_10ms)
{
in->mif.do_popup_now = True;
in->mif.is_popped_up_by_timeout = True;
}
}
}
if (in->mif.do_popup_now && med->mi == in->miRemovedSubmenu &&
!in->mif.is_key_press)
{
/* prevent popping up the menu again with
* RemoveSubemenus */
in->mif.do_popup_now = False;
in->mif.do_force_popup = False;
do_fake_motion = in->mif.do_popdown_now;
in->mif.is_popped_up_by_timeout = False;
}
if (do_fake_motion)
{
/* fake a motion event, and set in->mif.do_popup_now */
e.type = MotionNotify;
e.xmotion.time = fev_get_evtime();
fev_fake_event(&e);
in->mif.is_motion_faked = True;
break;
}
usleep(10000 /* 10 ms*/);
}
return;
}
static mloop_ret_code_t __mloop_get_event(
MenuParameters *pmp, MenuReturn *pmret,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
{
XEvent e = *(*pmp->pexc)->x.elast;
in->mif.do_popup_and_warp = False;
in->mif.do_popup_now = False;
in->mif.do_popdown_now = False;
in->mif.do_propagate_event_into_submenu = False;
in->mif.is_key_press = False;
in->mif.is_button_release = 0;
if (pmp->event_propagate_to_submenu)
{
/* handle an event that was passed in from the parent menu */
fev_fake_event(pmp->event_propagate_to_submenu);
pmp->event_propagate_to_submenu = NULL;
}
else if (in->mif.do_recycle_event)
{
in->mif.is_popped_up_by_timeout = False;
in->mif.do_recycle_event = 0;
if (pmp->menu != pmret->target_menu)
{
/* the event is for a previous menu, just close this
* one */
pmret->rc = MENU_PROPAGATE_EVENT;
return MENU_MLOOP_RET_END;
}
if ((*pmp->pexc)->x.elast->type == KeyPress)
{
/* since the pointer has been warped since the key was
* pressed, fake a different key press position */
if (FQueryPointer(
dpy, Scr.Root, &JunkRoot, &JunkChild,
&e.xkey.x_root, &e.xkey.y_root,
&JunkX, &JunkY, &e.xkey.state) == False)
{
/* pointer is on a different screen */
e.xkey.x_root = 0;
e.xkey.y_root = 0;
}
fev_fake_event(&e);
med->mi = MR_SELECTED_ITEM(pmp->menu);
}
}
else if (pmp->tear_off_root_menu_window != NULL &&
FCheckTypedWindowEvent(
dpy, FW_W_PARENT(pmp->tear_off_root_menu_window),
ClientMessage, &e))
{
/* Got a ClientMessage for the tear out menu */
}
else
{
if (in->mif.do_force_reposition)
{
e.type = MotionNotify;
e.xmotion.time = fev_get_evtime();
in->mif.is_motion_faked = True;
in->mif.do_force_reposition = False;
in->mif.is_popped_up_by_timeout = False;
fev_fake_event(&e);
}
else if (!FCheckMaskEvent(dpy, ExposureMask, &e))
{
Bool is_popdown_timer_active = False;
Bool is_popup_timer_active = False;
if (MST_POPDOWN_DELAY(pmp->menu) > 0 &&
in->mi_with_popup != NULL &&
in->mi_with_popup != MR_SELECTED_ITEM(pmp->menu))
{
is_popdown_timer_active = True;
}
if (MST_POPUP_DELAY(pmp->menu) > 0 &&
!in->mif.is_popped_up_by_timeout &&
in->mi_wants_popup != NULL)
{
is_popup_timer_active = True;
}
/* handle exposure events first */
if (in->mif.do_force_popup ||
in->mif.is_pointer_in_active_item_area ||
is_popdown_timer_active || is_popup_timer_active)
{
__mloop_get_event_timeout_loop(
pmp, in, med, msi);
}
else
{
/* block until there is an event */
FMaskEvent(dpy, msi->event_mask, &e);
in->mif.is_popped_up_by_timeout = False;
}
}
else
{
in->mif.is_popped_up_by_timeout = False;
}
}
in->mif.is_pointer_in_active_item_area = False;
if (e.type == MotionNotify)
{
/* discard any extra motion events before a release */
while (FCheckMaskEvent(
dpy, ButtonMotionMask | ButtonReleaseMask,
&e) && (e.type != ButtonRelease))
{
/* nothing */
}
}
return MENU_MLOOP_RET_NORMAL;
}
static mloop_ret_code_t __mloop_handle_event(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi)
{
MenuRoot *tmrMi;
Bool rc;
pmret->rc = MENU_NOP;
switch ((*pmp->pexc)->x.elast->type)
{
case ButtonRelease:
in->mif.is_button_release = 1;
med->mi = find_entry(
pmp, &med->x_offset, &med->mrMi,
(*pmp->pexc)->x.elast->xbutton.subwindow,
(*pmp->pexc)->x.elast->xbutton.x_root,
(*pmp->pexc)->x.elast->xbutton.y_root);
/* hold the menu up when the button is released
* for the first time if released OFF of the menu */
if (pmp->flags.is_sticky && !in->mif.is_motion_first)
{
in->mif.is_release_first = True;
pmp->flags.is_sticky = False;
return MENU_MLOOP_RET_LOOP;
}
if (med->mrMi != NULL)
{
int menu_x;
int menu_y;
menu_shortcuts(
med->mrMi, pmp, pmret, (*pmp->pexc)->x.elast,
&med->mi, pdkp, &menu_x, &menu_y);
if (pmret->rc == MENU_NEWITEM_MOVEMENU)
{
move_any_menu(med->mrMi, pmp, menu_x, menu_y);
pmret->rc = MENU_NEWITEM;
}
else if (pmret->rc == MENU_NEWITEM_FIND)
{
med->mi = find_entry(
pmp, NULL, NULL, None, -1, -1);
pmret->rc = MENU_NEWITEM;
}
}
else
{
pmret->rc = MENU_SELECTED;
}
switch (pmret->rc)
{
case MENU_NOP:
return MENU_MLOOP_RET_LOOP;
case MENU_POPDOWN:
case MENU_ABORTED:
case MENU_DOUBLE_CLICKED:
case MENU_TEAR_OFF:
case MENU_KILL_TEAR_OFF_MENU:
return MENU_MLOOP_RET_END;
case MENU_POPUP:
/* Allow for MoveLeft/MoveRight action to work with
* Mouse */
in->mif.do_popup_and_warp = True;
case MENU_NEWITEM:
/* unpost the menu if posted */
pmret->flags.is_menu_posted = 0;
return MENU_MLOOP_RET_NORMAL;
case MENU_SELECTED:
in->mif.was_item_unposted = 0;
if (pmret->flags.is_menu_posted &&
med->mrMi != NULL)
{
if (pmret->flags.do_unpost_submenu)
{
pmret->flags.do_unpost_submenu = 0;
pmret->flags.is_menu_posted = 0;
/* just ignore the event */
return MENU_MLOOP_RET_LOOP;
}
else if (med->mi && MI_IS_POPUP(med->mi)
&& med->mrMi == pmp->menu)
{
/* post menu - done below */
}
else if (MR_PARENT_ITEM(pmp->menu) &&
med->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->target_menu = med->mrMi;
return MENU_MLOOP_RET_END;
}
else if (med->mrMi != pmp->menu &&
med->mrMi != in->mrPopup)
{
/* unpost and propagate back to
* ancestor */
pmret->flags.is_menu_posted = 0;
pmret->rc = MENU_PROPAGATE_EVENT;
pmret->target_menu = med->mrMi;
return MENU_MLOOP_RET_END;
}
else if (in->mrPopup && med->mrMi ==
in->mrPopup)
{
/* unpost and propagate into
* submenu */
in->mif.was_item_unposted = 1;
in->mif.do_propagate_event_into_submenu
= True;
break;
}
else
{
/* simply unpost the menu */
pmret->flags.is_menu_posted = 0;
}
}
if (is_double_click(
msi->t0, med->mi, pmp, pmret, pdkp,
in->mif.has_mouse_moved))
{
pmret->rc = MENU_DOUBLE_CLICKED;
return MENU_MLOOP_RET_END;
}
if (med->mi == NULL)
{
pmret->rc = MENU_ABORTED;
}
else if (MI_IS_POPUP(med->mi))
{
switch (MST_DO_POPUP_AS(pmp->menu))
{
case MDP_POST_MENU:
if (in->mif.was_item_unposted)
{
pmret->flags.is_menu_posted =
0;
pmret->rc = MENU_UNPOST;
pmret->target_menu = NULL;
in->mif.do_popup_now = False;
}
else
{
pmret->flags.is_menu_posted =
1;
pmret->rc = MENU_POST;
pmret->target_menu = NULL;
in->mif.do_popup_now = True;
if ((in->mrPopup ||
in->mrPopdown)
&& med->mi !=
MR_SELECTED_ITEM(
pmp->menu))
{
in->mif.do_popdown_now
= True;
}
}
return MENU_MLOOP_RET_NORMAL;
case MDP_IGNORE:
pmret->rc = MENU_NOP;
return MENU_MLOOP_RET_NORMAL;
case MDP_CLOSE:
pmret->rc = MENU_ABORTED;
break;
case MDP_ROOT_MENU:
default:
break;
}
}
break;
default:
break;
}
pdkp->timestamp = 0;
return MENU_MLOOP_RET_END;
case ButtonPress:
/* if the first event is a button press allow the release to
* select something */
pmp->flags.is_sticky = False;
return MENU_MLOOP_RET_LOOP;
case VisibilityNotify:
return MENU_MLOOP_RET_LOOP;
case KeyRelease:
if ((*pmp->pexc)->x.elast->xkey.keycode !=
MST_SELECT_ON_RELEASE_KEY(pmp->menu))
{
return MENU_MLOOP_RET_LOOP;
}
/* fall through to KeyPress */
case KeyPress:
/* Handle a key press events to allow mouseless operation */
in->mif.is_key_press = True;
med->x_offset = menudim_middle_x_offset(&MR_DIM(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;
XEvent e;
pmret->flags.is_menu_posted = 0;
l_mi = find_entry(
pmp, &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->target_menu = l_mrMi;
return MENU_MLOOP_RET_END;
}
}
med->mi = MR_SELECTED_ITEM(pmp->menu);
e = *(*pmp->pexc)->x.elast;
e.xkey.x_root = med->x_offset;
e.xkey.y_root = menuitem_middle_y_offset(
med->mi, MR_STYLE(pmp->menu));
fev_fake_event(&e);
}
/* now handle the actual key press */
{
int menu_x;
int menu_y;
menu_shortcuts(
pmp->menu, pmp, pmret, (*pmp->pexc)->x.elast,
&med->mi, pdkp, &menu_x, &menu_y);
if (pmret->rc == MENU_NEWITEM_MOVEMENU)
{
move_any_menu(pmp->menu, pmp, menu_x, menu_y);
pmret->rc = MENU_NEWITEM;
}
else if (pmret->rc == MENU_NEWITEM_FIND)
{
med->mi = find_entry(
pmp, NULL, NULL, None, -1, -1);
pmret->rc = MENU_NEWITEM;
}
}
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 (med->mi && MI_IS_POPUP(med->mi))
{
switch (MST_DO_POPUP_AS(pmp->menu))
{
case MDP_POST_MENU:
pmret->rc = MENU_POPUP;
break;
case MDP_IGNORE:
pmret->rc = MENU_NOP;
return MENU_MLOOP_RET_NORMAL;
case MDP_CLOSE:
pmret->rc = MENU_ABORTED;
return MENU_MLOOP_RET_END;
case MDP_ROOT_MENU:
default:
return MENU_MLOOP_RET_END;
}
break;
}
return MENU_MLOOP_RET_END;
case MENU_POPDOWN:
case MENU_ABORTED:
case MENU_DOUBLE_CLICKED:
case MENU_TEAR_OFF:
case MENU_KILL_TEAR_OFF_MENU:
return MENU_MLOOP_RET_END;
case MENU_NEWITEM:
case MENU_POPUP:
if (med->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. */
med->mrMi = pmp->menu;
}
/*tmrMi = med->mrMi;*/
break;
default:
break;
}
/* now warp to the new menu item, if any */
if (pmret->rc == MENU_NEWITEM && med->mi)
{
warp_pointer_to_item(med->mrMi, med->mi, False);
}
if (pmret->rc == MENU_POPUP && med->mi && MI_IS_POPUP(med->mi))
{
in->mif.do_popup_and_warp = True;
break;
}
break;
case MotionNotify:
{
int p_rx = -1;
int p_ry = -1;
Window p_child = None;
if (in->mif.has_mouse_moved == False)
{
if (FQueryPointer(
dpy, Scr.Root, &JunkRoot, &p_child, &p_rx,
&p_ry, &JunkX, &JunkY, &JunkMask) == False)
{
/* pointer is on a different screen */
p_rx = 0;
p_ry = 0;
}
if (p_rx - msi->x_init > Scr.MoveThreshold ||
msi->x_init - p_rx > Scr.MoveThreshold ||
p_ry - msi->y_init > Scr.MoveThreshold ||
msi->y_init - p_ry > Scr.MoveThreshold)
{
/* remember that this isn't just a click any
* more since the pointer moved */
in->mif.has_mouse_moved = True;
}
}
med->mi = find_entry(
pmp, &med->x_offset, &med->mrMi, p_child, p_rx, p_ry);
if (pmret->flags.is_menu_posted &&
med->mrMi != pmret->target_menu)
{
/* ignore mouse movement outside a posted menu */
med->mrMi = NULL;
med->mi = NULL;
}
if (!in->mif.is_release_first && !in->mif.is_motion_faked &&
in->mif.has_mouse_moved)
{
in->mif.is_motion_first = True;
}
in->mif.is_motion_faked = False;
break;
}
case Expose:
/* grab our expose events, let the rest go through */
XFlush(dpy);
rc = menu_expose((*pmp->pexc)->x.elast, (*pmp->pexc)->w.fw);
/* we want to dispatch this too so that icons and maybe tear
* off get redrawn after being obscured by menus. */
if (rc == False)
{
dispatch_event((*pmp->pexc)->x.elast);
}
return MENU_MLOOP_RET_LOOP;
case ClientMessage:
if ((*pmp->pexc)->x.elast->xclient.format == 32 &&
(*pmp->pexc)->x.elast->xclient.data.l[0] ==
_XA_WM_DELETE_WINDOW &&
pmp->tear_off_root_menu_window != NULL &&
(*pmp->pexc)->x.elast->xclient.window == FW_W_PARENT(
pmp->tear_off_root_menu_window))
{
/* handle deletion of tear out menus */
pmret->rc = MENU_KILL_TEAR_OFF_MENU;
return MENU_MLOOP_RET_END;
}
break;
case EnterNotify:
/* ignore EnterNotify events */
break;
case LeaveNotify:
if (pmp->tear_off_root_menu_window != NULL &&
find_entry(pmp, NULL, &tmrMi, None, -1, -1) == NULL &&
tmrMi == NULL)
{
/* handle deletion of tear out menus */
pmret->rc = MENU_ABORTED;
return MENU_MLOOP_RET_END;
}
/* ignore it */
return MENU_MLOOP_RET_LOOP;
case UnmapNotify:
/* should never happen, but does not hurt */
if (pmp->tear_off_root_menu_window != NULL &&
(*pmp->pexc)->x.elast->xunmap.window ==
FW_W(pmp->tear_off_root_menu_window))
{
/* handle deletion of tear out menus */
pmret->rc = MENU_KILL_TEAR_OFF_MENU;
/* extra safety: pass event back to main event loop to
* make sure the window is destroyed */
FPutBackEvent(dpy, (*pmp->pexc)->x.elast);
return MENU_MLOOP_RET_END;
}
break;
default:
/* We must not dispatch events here. There is no guarantee
* that dispatch_event doesn't destroy a window stored in the
* menu structures. Anyway, no events should ever get here
* except to tear off menus and these must be handled
* individually. */
#if 0
dispatch_event((*pmp->pexc)->x.elast);
#endif
break;
}
return MENU_MLOOP_RET_NORMAL;
}
static void __mloop_select_item(
MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
Bool does_submenu_overlap, Bool *pdoes_popdown_submenu_overlap)
{
in->mif.is_item_entered_by_key_press = in->mif.is_key_press;
med->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 (in->mrPopup && in->mrPopup != in->mrPopdown)
{
in->mrPopdown = in->mrPopup;
med->popdown_delay_10ms = 0;
*pdoes_popdown_submenu_overlap = does_submenu_overlap;
in->mrPopup = NULL;
}
select_menu_item(
pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
(*pmp->pexc)->w.fw);
}
/* highlight the new item; sets MR_SELECTED_ITEM(pmp->menu) too */
select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
return;
}
static void __mloop_wants_popup(
MenuParameters *pmp, mloop_evh_input_t *in, mloop_evh_data_t *med,
MenuRoot *mrMiPopup)
{
Bool do_it_now = False;
in->mi_wants_popup = med->mi;
if (in->mif.do_popup_now)
{
do_it_now = True;
}
else if (MST_DO_POPUP_IMMEDIATELY(pmp->menu) &&
med->mi != in->miRemovedSubmenu)
{
if (in->mif.is_key_press ||
MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
{
do_it_now = True;
}
}
else if (pointer_in_active_item_area(med->x_offset, med->mrMi))
{
if (in->mif.is_key_press || med->mi == in->miRemovedSubmenu ||
MST_DO_POPDOWN_IMMEDIATELY(pmp->menu) || !in->mrPopdown)
{
do_it_now = True;
}
else
{
in->mif.is_pointer_in_active_item_area = 1;
}
}
if (do_it_now)
{
in->miRemovedSubmenu = NULL;
/* must create a new menu or popup */
if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
{
if (in->mif.do_popup_now)
{
in->mif.do_popup = True;
}
else
{
/* pop up in next pass through loop */
in->mif.do_force_popup = True;
}
}
else if (in->mif.do_popup_and_warp)
{
warp_pointer_to_item(
in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
}
}
return;
}
static mloop_ret_code_t __mloop_make_popup(
MenuParameters *pmp, MenuReturn *pmret,
mloop_evh_input_t *in, mloop_evh_data_t *med,
MenuOptions *pops, Bool *pdoes_submenu_overlap)
{
/* create a popup menu */
if (!is_submenu_mapped(pmp->menu, med->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(in->mrPopup, pmp);
get_prefered_popup_position(
pmp->menu, in->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(
&in->mrPopup, pmp, pmp->menu, med->mi, pmp->pexc, x, y,
prefer_left_submenus, in->mif.do_popup_and_warp, pops,
pdoes_submenu_overlap,
(in->mrPopdown) ? MR_WINDOW(in->mrPopdown) : None);
in->mi_with_popup = med->mi;
in->mi_wants_popup = NULL;
if (in->mrPopup == NULL)
{
/* the menu deleted itself when execution the dynamic
* popup * action */
pmret->rc = MENU_ERROR;
return MENU_MLOOP_RET_END;
}
MR_SUBMENU_ITEM(pmp->menu) = med->mi;
}
return MENU_MLOOP_RET_NORMAL;
}
static mloop_ret_code_t __mloop_get_mi_actions(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
Bool *pdoes_popdown_submenu_overlap)
{
in->mif.do_popdown = False;
in->mif.do_popup = False;
in->mif.do_menu = False;
in->mif.is_submenu_mapped = False;
if (!in->mrPopup && in->mrPopdown &&
med->mi == MR_PARENT_ITEM(in->mrPopdown))
{
/* We're again on the item that we left before.
* Deschedule popping it down. */
in->mrPopup = in->mrPopdown;
in->mrPopdown = NULL;
}
if (med->mrMi == in->mrPopup)
{
/* must make current popup menu a real menu */
in->mif.do_menu = True;
in->mif.is_submenu_mapped = True;
}
else if (pmret->rc == MENU_POST)
{
/* must create a real menu and warp into it */
if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
{
in->mif.do_popup = True;
}
in->mif.do_menu = True;
in->mif.is_submenu_mapped = True;
}
else if (in->mif.do_popup_and_warp)
{
/* must create a real menu and warp into it */
if (in->mrPopup == NULL || in->mrPopup != mrMiPopup)
{
in->mif.do_popup = True;
}
else
{
XRaiseWindow(dpy, MR_WINDOW(in->mrPopup));
warp_pointer_to_item(
in->mrPopup, MR_FIRST_ITEM(in->mrPopup), True);
in->mif.do_menu = True;
in->mif.is_submenu_mapped = True;
}
}
else if (med->mi && MI_IS_POPUP(med->mi))
{
if ((*pmp->pexc)->x.elast->type == ButtonPress &&
is_double_click(
msi->t0, med->mi, pmp, pmret, pdkp,
in->mif.has_mouse_moved))
{
pmret->rc = MENU_DOUBLE_CLICKED;
return MENU_MLOOP_RET_END;
}
else if (!in->mrPopup || in->mrPopup != mrMiPopup ||
in->mif.do_popup_and_warp)
{
__mloop_wants_popup(pmp, in, med, mrMiPopup);
}
}
if (in->mif.do_popdown_now)
{
in->mif.do_popdown = True;
in->mif.do_popdown_now = False;
}
else if (in->mif.do_popup)
{
if (in->mrPopup && in->mrPopup != mrMiPopup)
{
in->mif.do_popdown = True;
in->mrPopdown = in->mrPopup;
med->popdown_delay_10ms = 0;
*pdoes_popdown_submenu_overlap =
*pdoes_submenu_overlap;
}
else if (in->mrPopdown && in->mrPopdown != mrMiPopup)
{
in->mif.do_popdown = True;
}
else if (in->mrPopdown && in->mrPopdown == mrMiPopup)
{
in->mrPopup = in->mrPopdown;
*pdoes_submenu_overlap =
*pdoes_popdown_submenu_overlap;
in->mrPopdown = NULL;
in->mif.do_popup = False;
in->mif.do_popdown = False;
}
}
return MENU_MLOOP_RET_NORMAL;
}
static void __mloop_do_popdown(
MenuParameters *pmp, mloop_evh_input_t *in,
Bool *pdoes_popdown_submenu_overlap)
{
if (in->mrPopdown)
{
pop_menu_down_and_repaint_parent(
&in->mrPopdown, pdoes_popdown_submenu_overlap, pmp);
in->mi_with_popup = NULL;
MR_SUBMENU_ITEM(pmp->menu) = NULL;
if (in->mrPopup == in->mrPopdown)
{
in->mrPopup = NULL;
}
in->mrPopdown = NULL;
}
in->mif.do_popdown = False;
return;
}
static mloop_ret_code_t __mloop_do_popup(
MenuParameters *pmp, MenuReturn *pmret,
mloop_evh_input_t *in, mloop_evh_data_t *med,
MenuOptions *pops, MenuRoot *mrMiPopup, Bool *pdoes_submenu_overlap,
Bool *pdoes_popdown_submenu_overlap)
{
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->pexc)->w.fw);
}
/* get pos hints for item's action */
get_popup_options(pmp, med->mi, pops);
if (med->mrMi == pmp->menu && mrMiPopup == NULL &&
MI_IS_POPUP(med->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 is_busy_grabbed = False;
char *menu_name;
char *action;
char *missing_action = MR_MISSING_SUBMENU_FUNC(pmp->menu);
menu_name = PeekToken(
SkipNTokens(MI_ACTION(med->mi), 1), NULL);
if (!menu_name)
{
menu_name = "";
}
is_complex_function =
functions_is_complex_function(missing_action);
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 */
__menu_execute_function(pmp->pexc, action);
if (is_complex_function)
{
free(action);
}
if (is_busy_grabbed)
{
UngrabEm(GRAB_BUSYMENU);
}
/* Let's see if the menu exists now. */
mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
} /* run MISSING_SUBMENU_FUNCTION */
in->mrPopup = mrMiPopup;
if (!in->mrPopup)
{
in->mif.do_menu = False;
pmret->flags.is_menu_posted = 0;
}
else
{
if (__mloop_make_popup(
pmp, pmret, in, med, pops, pdoes_submenu_overlap)
== MENU_MLOOP_RET_END)
{
return MENU_MLOOP_RET_END;
}
} /* else (in->mrPopup) */
if (in->mif.do_popdown)
{
if (in->mrPopdown)
{
if (in->mrPopdown != in->mrPopup)
{
if (in->mi_with_popup ==
MR_PARENT_ITEM(in->mrPopdown))
{
in->mi_with_popup = NULL;
}
pop_menu_down_and_repaint_parent(
&in->mrPopdown,
pdoes_popdown_submenu_overlap, pmp);
}
in->mrPopdown = NULL;
}
in->mif.do_popdown = False;
}
if (in->mrPopup)
{
if (MR_PARENT_MENU(in->mrPopup) == pmp->menu)
{
med->mi = find_entry(
pmp, NULL, &med->mrMi, None, -1, -1);
if (med->mi && med->mrMi == in->mrPopup)
{
in->mif.do_menu = True;
in->mif.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. */
in->mif.do_menu = False;
in->mif.do_popdown = False;
in->mrPopup = NULL;
}
}
return MENU_MLOOP_RET_NORMAL;
}
static mloop_ret_code_t __mloop_do_menu(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, MenuOptions *pops,
Bool *pdoes_submenu_overlap)
{
MenuParameters mp;
XEvent e;
memset(&mp, 0, sizeof(mp));
mp.menu = in->mrPopup;
mp.pexc = pmp->pexc;
mp.parent_menu = pmp->menu;
mp.parent_item = med->mi;
mp.tear_off_root_menu_window = pmp->tear_off_root_menu_window;
MR_IS_TEAR_OFF_MENU(in->mrPopup) = 0;
mp.flags.has_default_action = False;
mp.flags.is_already_mapped = in->mif.is_submenu_mapped;
mp.flags.is_sticky = False;
mp.flags.is_submenu = True;
mp.flags.is_triggered_by_keypress = !!in->mif.do_popup_and_warp;
mp.pops = pops;
mp.ret_paction = pmp->ret_paction;
mp.screen_origin_x = pmp->screen_origin_x;
mp.screen_origin_y = pmp->screen_origin_y;
if (in->mif.do_propagate_event_into_submenu)
{
e = *(*pmp->pexc)->x.elast;
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);
in->mif.do_propagate_event_into_submenu = False;
if (pmret->rc == MENU_PROPAGATE_EVENT)
{
in->mif.do_recycle_event = 1;
return MENU_MLOOP_RET_LOOP;
}
if (IS_MENU_RETURN(pmret->rc))
{
pdkp->timestamp = 0;
return MENU_MLOOP_RET_END;
}
if (MST_DO_UNMAP_SUBMENU_ON_POPDOWN(pmp->menu) &&
pmret->flags.is_key_press)
{
in->miRemovedSubmenu = MR_PARENT_ITEM(in->mrPopup);
pop_menu_down_and_repaint_parent(
&in->mrPopup, pdoes_submenu_overlap, pmp);
in->mi_with_popup = NULL;
MR_SUBMENU_ITEM(pmp->menu) = NULL;
if (in->mrPopup == in->mrPopdown)
{
in->mrPopdown = NULL;
}
in->mrPopup = NULL;
}
if (pmret->rc == MENU_POPDOWN)
{
med->popup_delay_10ms = 0;
in->mif.do_force_reposition = True;
}
return MENU_MLOOP_RET_NORMAL;
}
static mloop_ret_code_t __mloop_handle_action_with_mi(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
MenuOptions *pops, Bool *pdoes_submenu_overlap,
Bool *pdoes_popdown_submenu_overlap)
{
MenuItem *tmi;
MenuRoot *tmrMi;
MenuRoot *mrMiPopup = NULL;
pmret->flags.do_unpost_submenu = 0;
/* we're on a menu item */
in->mif.is_off_menu_allowed = False;
if (med->mrMi == pmp->menu && med->mi != in->miRemovedSubmenu)
{
in->miRemovedSubmenu = NULL;
}
if (med->mrMi != pmp->menu && med->mrMi != in->mrPopup &&
med->mrMi != in->mrPopdown)
{
/* we're on an item from a prior menu */
if (med->mrMi != MR_PARENT_MENU(pmp->menu))
{
/* the event is for a previous menu, just close
* this one */
pmret->rc = MENU_PROPAGATE_EVENT;
pmret->target_menu = med->mrMi;
}
else
{
pmret->rc = MENU_POPDOWN;
}
pdkp->timestamp = 0;
return MENU_MLOOP_RET_END;
}
if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi == pmp->menu)
{
/* new item of the same menu */
__mloop_select_item(
pmp, in, med, *pdoes_submenu_overlap,
pdoes_popdown_submenu_overlap);
}
else if (med->mi != MR_SELECTED_ITEM(pmp->menu) && med->mrMi &&
med->mrMi == in->mrPopdown)
{
/* we're on the popup menu of a different menu item of this
* menu */
med->mi = MR_PARENT_ITEM(in->mrPopdown);
in->mrPopup = in->mrPopdown;
in->mrPopdown = NULL;
*pdoes_submenu_overlap = *pdoes_popdown_submenu_overlap;
select_menu_item(pmp->menu, med->mi, True, (*pmp->pexc)->w.fw);
}
mrMiPopup = mr_popup_for_mi(pmp->menu, med->mi);
/* check what has to be done with the item */
if (__mloop_get_mi_actions(
pmp, pmret, pdkp, in, med, msi, mrMiPopup,
pdoes_submenu_overlap, pdoes_popdown_submenu_overlap) ==
MENU_MLOOP_RET_END)
{
return MENU_MLOOP_RET_END;
}
/* do what needs to be done */
if (in->mif.do_popdown && !in->mif.do_popup)
{
/* popdown previous popup */
__mloop_do_popdown(pmp, in, pdoes_popdown_submenu_overlap);
}
if (in->mif.do_popup)
{
if (__mloop_do_popup(
pmp, pmret, in, med, pops, mrMiPopup,
pdoes_submenu_overlap,
pdoes_popdown_submenu_overlap) ==
MENU_MLOOP_RET_END)
{
return MENU_MLOOP_RET_END;
}
}
/* remember the 'posted' menu */
if (pmret->flags.is_menu_posted && in->mrPopup != NULL &&
pmret->target_menu == NULL)
{
pmret->target_menu = in->mrPopup;
}
else if (pmret->flags.is_menu_posted && in->mrPopup == NULL)
{
pmret->flags.is_menu_posted = 0;
}
if (in->mif.do_menu)
{
mloop_ret_code_t rc;
rc = __mloop_do_menu(
pmp, pmret, pdkp, in, med, pops,
pdoes_submenu_overlap);
if (rc != MENU_MLOOP_RET_NORMAL)
{
return rc;
}
}
/* 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 (in->mrPopup && MR_XANIMATION(in->mrPopup) &&
(tmi = find_entry(pmp, NULL, &tmrMi, None, -1, -1)) &&
(tmi == MR_SELECTED_ITEM(pmp->menu) || tmrMi != pmp->menu))
{
animated_move_back(in->mrPopup, False, (*pmp->pexc)->w.fw);
}
/* 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 */
/* but this cause terrible back-and-forth under certain circonstance,
* I think we should disable this ... 2002-09-17 olicha */
if (in->mrPopup == NULL && pmp->parent_menu != NULL &&
MR_XANIMATION(pmp->menu) != 0 &&
pointer_in_passive_item_area(med->x_offset, med->mrMi))
{
/* we have to see if we need menu to be moved */
animated_move_back(pmp->menu, True, (*pmp->pexc)->w.fw);
if (in->mrPopdown)
{
if (in->mrPopdown != in->mrPopup)
{
pop_menu_down_and_repaint_parent(
&in->mrPopdown,
pdoes_popdown_submenu_overlap, pmp);
in->mi_with_popup = NULL;
}
in->mrPopdown = NULL;
}
in->mif.do_popdown = False;
}
return MENU_MLOOP_RET_NORMAL;
}
static mloop_ret_code_t __mloop_handle_action_without_mi(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
MenuOptions *pops, Bool *pdoes_submenu_overlap,
Bool *pdoes_popdown_submenu_overlap)
{
pmret->flags.do_unpost_submenu = 0;
/* moved off menu, deselect selected item... */
if (!MR_SELECTED_ITEM(pmp->menu) ||
in->mif.is_off_menu_allowed == True || pmret->flags.is_menu_posted)
{
/* nothing to do */
return MENU_MLOOP_RET_NORMAL;
}
if (in->mrPopup)
{
int x, y, mx, my;
int mw, mh;
if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
&x, &y, &JunkX, &JunkY, &JunkMask) == False)
{
/* pointer is on a different screen */
x = 0;
y = 0;
}
if (menu_get_geometry(
pmp->menu, &JunkRoot, &mx, &my, &mw, &mh, &JunkBW,
&JunkDepth) &&
((!MR_IS_LEFT(in->mrPopup) && x < mx) ||
(!MR_IS_RIGHT(in->mrPopup) && x > mx + mw) ||
(!MR_IS_UP(in->mrPopup) && y < my) ||
(!MR_IS_DOWN(in->mrPopup) && y > my + mh)))
{
select_menu_item(
pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
(*pmp->pexc)->w.fw);
pop_menu_down_and_repaint_parent(
&in->mrPopup, pdoes_submenu_overlap, pmp);
in->mi_with_popup = NULL;
MR_SUBMENU_ITEM(pmp->menu) = NULL;
if (in->mrPopup == in->mrPopdown)
{
in->mrPopdown = NULL;
}
in->mrPopup = NULL;
}
else if (x < mx || x >= mx + mw || y < my || y >= my + mh)
{
/* pointer is outside the menu but do not pop down */
in->mif.is_off_menu_allowed = True;
}
else
{
/* Pointer is still in the menu. Postpone the decision
* if we have to pop down. */
}
} /* if (in->mrPopup) */
else
{
select_menu_item(
pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
(*pmp->pexc)->w.fw);
}
return MENU_MLOOP_RET_NORMAL;
}
static void __mloop_exit_warp_back(MenuParameters *pmp)
{
MenuItem *tmi;
MenuRoot *tmrMi;
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(pmp, 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));
}
}
return;
}
static void __mloop_exit_select_in_place(
MenuParameters *pmp, mloop_evh_data_t *med, MenuOptions *pops)
{
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, med->mi);
if (submenu && MR_WINDOW(submenu) != None &&
XGetWindowAttributes(dpy, MR_WINDOW(submenu), &win_attribs) &&
win_attribs.map_state == IsViewable &&
menu_get_geometry(
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 (pops->flags.has_poshints)
{
last_saved_pos_hints.pos_hints = pops->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 (pops->flags.do_warp_on_select)
{
last_saved_pos_hints.flags.do_warp_title = 1;
}
return;
}
static void __mloop_exit_selected(
MenuParameters *pmp, MenuReturn *pmret,
mloop_evh_data_t *med, MenuOptions *pops)
{
/* save action to execute so that the menu may be destroyed
* now */
if (pmp->ret_paction)
{
if (*pmp->ret_paction)
{
free(*pmp->ret_paction);
}
*pmp->ret_paction = (med->mi) ?
safestrdup(MI_ACTION(med->mi)) : NULL;
}
if (pmp->ret_paction && *pmp->ret_paction && med->mi &&
MI_IS_POPUP(med->mi))
{
get_popup_options(pmp, med->mi, pops);
if (pops->flags.do_select_in_place)
{
__mloop_exit_select_in_place(pmp, med, pops);
}
else
{
last_saved_pos_hints.flags.do_ignore_pos_hints = True;
} /* pops->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;
}
return;
}
static void __mloop_exit(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp,
mloop_evh_input_t *in, mloop_evh_data_t *med, mloop_static_info_t *msi,
MenuOptions *pops)
{
Bool no = False;
Bool do_deselect = False;
if (in->mrPopdown)
{
pop_menu_down_and_repaint_parent(&in->mrPopdown, &no, pmp);
MR_SUBMENU_ITEM(pmp->menu) = NULL;
}
if (pmret->rc == MENU_SELECTED && is_double_click(
msi->t0, med->mi, pmp, pmret, pdkp,
in->mif.has_mouse_moved))
{
pmret->rc = MENU_DOUBLE_CLICKED;
}
if (pmret->rc == MENU_SELECTED && med->mi &&
MI_FUNC_TYPE(med->mi) == F_TEARMENUOFF)
{
pmret->rc = (MR_IS_TEAR_OFF_MENU(pmp->menu)) ?
MENU_KILL_TEAR_OFF_MENU : MENU_TEAR_OFF;
}
switch (pmret->rc)
{
case MENU_POPDOWN:
case MENU_PROPAGATE_EVENT:
case MENU_DOUBLE_CLICKED:
do_deselect = True;
/* Allow popdown to warp back pointer to main menu with mouse
button control. (MoveLeft/MoveRight on a mouse binding) */
if (((pmret->rc == MENU_POPDOWN && in->mif.is_button_release)
|| in->mif.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;
}
__mloop_exit_warp_back(pmp);
}
break;
case MENU_ABORTED:
case MENU_TEAR_OFF:
case MENU_KILL_TEAR_OFF_MENU:
do_deselect = True;
break;
case MENU_SELECTED:
__mloop_exit_selected(pmp, pmret, med, pops);
pmret->rc = MENU_DONE;
break;
default:
break;
}
if (do_deselect && MR_SELECTED_ITEM(pmp->menu))
{
select_menu_item(
pmp->menu, MR_SELECTED_ITEM(pmp->menu), False,
(*pmp->pexc)->w.fw);
}
if (pmret->rc == MENU_SUBMENU_TORN_OFF)
{
in->mrPopup = NULL;
MR_SUBMENU_ITEM(pmp->menu) = NULL;
}
if (in->mrPopup)
{
pop_menu_down_and_repaint_parent(&in->mrPopup, &no, pmp);
MR_SUBMENU_ITEM(pmp->menu) = NULL;
}
pmret->flags.is_key_press = in->mif.is_key_press;
return;
}
static void __menu_loop(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
{
mloop_evh_input_t mei;
mloop_ret_code_t mloop_ret;
mloop_evh_data_t med;
mloop_static_info_t msi;
MenuOptions mops;
Bool is_finished;
Bool does_submenu_overlap = False;
Bool does_popdown_submenu_overlap = False;
__mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
for (is_finished = False; !is_finished; )
{
mloop_ret = __mloop_get_event(pmp, pmret, &mei, &med, &msi);
switch (mloop_ret)
{
case MENU_MLOOP_RET_END:
is_finished = True;
case MENU_MLOOP_RET_LOOP:
continue;
default:
break;
}
mloop_ret = __mloop_handle_event(
pmp, pmret, pdkp, &mei, &med, &msi);
switch (mloop_ret)
{
case MENU_MLOOP_RET_END:
is_finished = True;
case MENU_MLOOP_RET_LOOP:
continue;
default:
break;
}
/* Now handle new menu items, whether it is from a keypress or
* a pointer motion event. */
if (med.mi != NULL)
{
mloop_ret = __mloop_handle_action_with_mi(
pmp, pmret, pdkp, &mei, &med, &msi, &mops,
&does_submenu_overlap,
&does_popdown_submenu_overlap);
}
else
{
mloop_ret = __mloop_handle_action_without_mi(
pmp, pmret, pdkp, &mei, &med, &msi, &mops,
&does_submenu_overlap,
&does_popdown_submenu_overlap);
}
if (mloop_ret == MENU_MLOOP_RET_END)
{
is_finished = True;
}
XFlush(dpy);
}
__mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
return;
}
/*
* Functions dealing with tear off menus
*/
static char *menu_strip_tear_off_title(MenuRoot *mr)
{
MenuItem *mi;
int i;
int len;
char *name;
for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
{
if (MI_IS_TITLE(mi))
{
break;
}
else if (!MI_IS_SEPARATOR(mi) && !MI_IS_TEAR_OFF_BAR(mi))
{
/* a normal item, no title found */
return NULL;
}
/* skip separators and tear off bars */
}
if (mi == NULL || !MI_HAS_TEXT(mi) || MI_NEXT_ITEM(mi) == NULL)
{
return NULL;
}
/* extract the window title from the labels */
for (i = 0, len = 0; i < MAX_MENU_ITEM_LABELS; i++)
{
if (MI_LABEL(mi)[i] != 0)
{
len += strlen(MI_LABEL(mi)[i]) + 1;
}
}
if (len == 0)
{
return NULL;
}
name = safemalloc(len + 1);
*name = 0;
for (i = 0; i < MAX_MENU_ITEM_LABELS; i++)
{
if (MI_LABEL(mi)[i] != 0)
{
strcat(name, MI_LABEL(mi)[i]);
strcat(name, " ");
}
}
/* strip the last space */
name[len - 1] = 0;
/* unlink and destroy the item */
unlink_item_from_menu(mr, mi);
menuitem_free(mi);
return name;
}
static void menu_tear_off(MenuRoot *mr_to_copy)
{
MenuRoot *mr;
MenuStyle *ms;
XEvent ev;
XSizeHints menusizehints;
XClassHint menuclasshints;
XTextProperty menunametext;
XWMHints menuwmhints;
char *list[] ={ NULL, NULL };
char *t;
char *name = NULL;
Atom protocols[1];
int x = 0;
int y = 0;
unsigned int add_mask = 0;
initial_window_options_t win_opts;
evh_args_t ea;
exec_context_changes_t ecc;
char *buffer;
char *action;
cond_rc_t *cond_rc = NULL;
const exec_context_t *exc = NULL;
/* keep the menu open */
if (MR_WINDOW(mr_to_copy) != None)
{
discard_window_events(
MR_WINDOW(mr_to_copy), SubstructureNotifyMask);
}
mr = clone_menu(mr_to_copy);
/* also dump the menu style */
buffer = (char *)safemalloc(23);
sprintf(buffer,"%lu",(unsigned long)mr);
action = buffer;
ms = menustyle_parse_style(F_PASS_ARGS);
if (!ms)
{
/* this must never happen */
fvwm_msg(
ERR, "menu_tear_off",
"impossible to create %s menu style", buffer);
free(buffer);
DestroyMenu(mr, False, False);
return;
}
free(buffer);
menustyle_copy(MR_STYLE(mr_to_copy),ms);
MR_STYLE(mr) = ms;
MST_USAGE_COUNT(mr) = 0;
name = menu_strip_tear_off_title(mr);
/* create the menu window and size the menu */
make_menu(mr, True);
/* set position */
if (menu_get_geometry(
mr_to_copy, &JunkRoot, &x, &y, &JunkWidth, &JunkHeight,
&JunkBW, &JunkDepth))
{
add_mask = PPosition;
XMoveWindow(dpy, MR_WINDOW(mr), x, y);
}
else
{
add_mask = 0;
}
/* focus policy */
menuwmhints.flags = InputHint;
menuwmhints.input = False;
/* size hints */
menusizehints.flags = PBaseSize | PMinSize | PMaxSize | add_mask;
menusizehints.base_width = 0;
menusizehints.base_height = 0;
menusizehints.min_width = MR_WIDTH(mr);
menusizehints.min_height = MR_HEIGHT(mr);
menusizehints.max_width = MR_WIDTH(mr);
menusizehints.max_height = MR_HEIGHT(mr);
/* class, resource and names */
menuclasshints.res_name =
(name != NULL) ? name : safestrdup(MR_NAME(mr));
menuclasshints.res_class = safestrdup("fvwm_menu");
for (t = menuclasshints.res_name; t != NULL && *t != 0; t++)
{
/* replace tabs in the title with spaces */
if (*t == '\t')
{
*t = ' ';
}
}
list[0] = menuclasshints.res_name;
menunametext.value = NULL;
XStringListToTextProperty(list, 1, &menunametext);
/* set all properties and hints */
XSetWMProperties(
dpy, MR_WINDOW(mr), &menunametext, &menunametext, NULL, 0,
&menusizehints, NULL, &menuclasshints);
XSetWMHints(dpy, MR_WINDOW(mr), &menuwmhints);
protocols[0] = _XA_WM_DELETE_WINDOW;
XSetWMProtocols(dpy, MR_WINDOW(mr), &(protocols[0]), 1);
/* free memory */
if (menunametext.value != NULL)
{
XFree(menunametext.value);
}
free(menuclasshints.res_class);
free(menuclasshints.res_name);
/* manage the window */
memset(&win_opts, 0, sizeof(win_opts));
win_opts.flags.is_menu = True;
ev.type = MapRequest;
ev.xmaprequest.send_event = True;
ev.xmaprequest.display = dpy;
ev.xmaprequest.parent = Scr.Root;
ev.xmaprequest.window = MR_WINDOW(mr);
fev_fake_event(&ev);
ecc.type = EXCT_NULL;
ecc.x.etrigger = &ev;
ecc.w.w = MR_WINDOW(mr);
ecc.w.wcontext = C_ROOT;
ea.exc = exc_create_context(
&ecc, ECC_TYPE | ECC_ETRIGGER | ECC_W | ECC_WCONTEXT);
HandleMapRequestKeepRaised(&ea, None, NULL, &win_opts);
exc_destroy_context(ea.exc);
return;
}
static void do_menu_close_tear_off_menu(MenuRoot *mr, MenuParameters mp)
{
pop_menu_down(&mr, &mp);
menustyle_free(MR_STYLE(mr));
DestroyMenu(mr, False, False);
}
/* ---------------------------- interface functions ------------------------- */
void menus_init(void)
{
memset((&Menus), 0, sizeof(MenuInfo));
return;
}
/* menus_find_menu expects a token as the input. Make sure you have used
* GetNextToken before passing a menu name to remove quotes (if necessary) */
MenuRoot *menus_find_menu(char *name)
{
MenuRoot *mr;
if (name == NULL)
{
return NULL;
}
for (mr = Menus.all; mr != NULL; mr = MR_NEXT_MENU(mr))
{
if (!MR_IS_DESTROYED(mr) && mr == MR_ORIGINAL_MENU(mr) &&
MR_NAME(mr) != NULL && StrEquals(name, MR_NAME(mr)))
{
break;
}
}
return mr;
}
void menus_remove_style_from_menus(MenuStyle *ms)
{
MenuRoot *mr;
for (mr = Menus.all; mr; mr = MR_NEXT_MENU(mr))
{
if (MR_STYLE(mr) == ms)
{
MR_STYLE(mr) = menustyle_get_default_style();
MR_IS_UPDATED(mr) = 1;
}
}
return;
}
/*
* Functions dealing with tear off menus
*/
void menu_enter_tear_off_menu(const exec_context_t *exc)
{
MenuRoot *mr;
char *ret_action = NULL;
MenuOptions mops;
MenuParameters mp;
MenuReturn mret;
const exec_context_t *exc2;
exec_context_changes_t ecc;
if (XFindContext(
dpy, FW_W(exc->w.fw), MenuContext,
(caddr_t *)&mr) == XCNOENT)
{
return;
}
ecc.w.fw = NULL;
ecc.w.w = None;
ecc.w.wcontext = C_ROOT;
exc2 = exc_clone_context(exc, &ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
memset(&mops, 0, sizeof(mops));
memset(&mret, 0, sizeof(MenuReturn));
memset(&mp, 0, sizeof(mp));
mp.menu = mr;
mp.pexc = &exc2;
mp.tear_off_root_menu_window = exc->w.fw;
MR_IS_TEAR_OFF_MENU(mr) = 1;
mp.flags.has_default_action = 0;
mp.flags.is_already_mapped = True;
mp.flags.is_sticky = False;
mp.flags.is_submenu = False;
mp.pops = &mops;
mp.ret_paction = &ret_action;
do_menu(&mp, &mret);
exc_destroy_context(exc2);
return;
}
void menu_close_tear_off_menu(FvwmWindow *fw)
{
MenuRoot *mr;
MenuParameters mp;
const exec_context_t *exc;
exec_context_changes_t ecc;
if (XFindContext(
dpy, FW_W(fw), MenuContext, (caddr_t *)&mr) == XCNOENT)
{
return;
}
memset(&mp, 0, sizeof(mp));
mp.menu = mr;
ecc.w.fw = NULL;
ecc.w.w = None;
ecc.w.wcontext = C_ROOT;
exc = exc_create_context(&ecc, ECC_FW | ECC_W | ECC_WCONTEXT);
mp.pexc = &exc;
do_menu_close_tear_off_menu(mr, mp);
exc_destroy_context(exc);
return;
}
Bool menu_redraw_transparent_tear_off_menu(FvwmWindow *fw, Bool pr_only)
{
MenuRoot *mr;
MenuStyle *ms = NULL;
int cs;
if (!(IS_TEAR_OFF_MENU(fw) &&
XFindContext(dpy, FW_W(fw), MenuContext,(caddr_t *)&mr) !=
XCNOENT &&
(ms = MR_STYLE(mr)) &&
ST_HAS_MENU_CSET(ms)))
{
return False;
}
cs = ST_CSET_MENU(ms);
if (!CSET_IS_TRANSPARENT(cs) ||
(pr_only && !CSET_IS_TRANSPARENT_PR(cs)))
{
return False;
}
return UpdateBackgroundTransparency(
dpy, MR_WINDOW(mr), MR_WIDTH(mr),
MR_HEIGHT(mr),
&Colorset[ST_CSET_MENU(ms)],
Pdepth,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
True);
}
/*
*
* 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
* do_menu() may destroy the *pmp->pexec member and replace it with a new
* copy. Be sure not to rely on the original structure being kept intact
* when calling do_menu().
*/
void do_menu(MenuParameters *pmp, MenuReturn *pmret)
{
int x;
int y;
Bool fWasAlreadyPopped = False;
Bool key_press;
Bool is_pointer_grabbed = False;
Bool is_pointer_ungrabbed = False;
Bool do_menu_interaction;
Bool do_check_pop_down;
Bool do_warp;
Time t0 = fev_get_evtime();
XEvent tmpevent;
double_keypress dkp;
/* 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;
int scr_w, scr_h;
pmret->rc = MENU_NOP;
if (pmp->flags.is_sticky && !pmp->flags.is_submenu)
{
FCheckTypedEvent(dpy, ButtonPressMask, &tmpevent);
}
if (pmp->menu == NULL)
{
pmret->rc = MENU_ERROR;
return;
}
key_press = pmp->flags.is_triggered_by_keypress;
/* Try to pick a root-relative optimal x,y to
* put the mouse right on the title w/o warping */
if (FQueryPointer(dpy, Scr.Root, &JunkRoot, &JunkChild,
&x, &y, &JunkX, &JunkY, &JunkMask) == False)
{
/* pointer is on a different screen */
x = 0;
y = 0;
}
/* 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;
}
is_pointer_grabbed = True;
/* 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 -= menudim_middle_x_offset(&MR_DIM(pmp->menu));
y -= menuitem_middle_y_offset(
MR_FIRST_ITEM(pmp->menu), MR_STYLE(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->pexc, x, y, prefer_left_submenus, do_warp,
pmp->pops, NULL, None))
{
XBell(dpy, 0);
UngrabEm(GRAB_MENU);
pmret->rc = MENU_ERROR;
return;
}
}
else
{
fWasAlreadyPopped = True;
if (pmp->tear_off_root_menu_window != NULL)
{
if (!GrabEm(CRS_MENU, GRAB_MENU))
{
/* GrabEm specifies the cursor to use */
XBell(dpy, 0);
pmret->rc = MENU_ABORTED;
return;
}
is_pointer_grabbed = True;
}
if (key_press)
{
warp_pointer_to_item(
pmp->menu, MR_FIRST_ITEM(pmp->menu),
True /* skip Title */);
}
}
/* Remember the key that popped up the root menu. */
if (pmp->flags.is_submenu)
{
dkp.timestamp = 0;
}
else
{
if (pmp->flags.is_triggered_by_keypress)
{
/* we have a real key event */
dkp.keystate = (*pmp->pexc)->x.etrigger->xkey.state;
dkp.keycode = (*pmp->pexc)->x.etrigger->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);
}
/* This may loop for tear off menus */
for (do_menu_interaction = True; do_menu_interaction == True; )
{
if (is_pointer_ungrabbed && !GrabEm(CRS_MENU, GRAB_MENU))
{
/* re-grab the pointer in this cycle */
XBell(dpy, 0);
pmret->rc = MENU_ABORTED;
break;
}
do_menu_interaction = False;
if (pmp->pops->flags.do_tear_off_immediately == 1)
{
pmret->rc = MENU_TEAR_OFF;
}
else
{
if (!pmp->flags.is_submenu)
{
XSelectInput(
dpy, Scr.NoFocusWin, XEVMASK_MENUNFW);
XFlush(dpy);
}
__menu_loop(pmp, pmret, &dkp);
if (!pmp->flags.is_submenu)
{
XSelectInput(
dpy, Scr.NoFocusWin, XEVMASK_NOFOCUSW);
XFlush(dpy);
}
}
do_check_pop_down = False;
switch (pmret->rc)
{
case MENU_TEAR_OFF:
menu_tear_off(pmp->menu);
pop_menu_down(&pmp->menu, pmp);
break;
case MENU_KILL_TEAR_OFF_MENU:
if (MR_IS_TEAR_OFF_MENU(pmp->menu))
{
/* kill the menu */
do_menu_close_tear_off_menu(pmp->menu, *pmp);
pmret->rc = MENU_ABORTED;
}
else
{
/* pass return code up to the torn off menu */
}
break;
case MENU_DOUBLE_CLICKED:
case MENU_DONE:
if (MR_IS_TEAR_OFF_MENU(pmp->menu))
{
do_menu_interaction = True;
}
else
{
do_check_pop_down = True;
}
break;
case MENU_SUBMENU_TORN_OFF:
pmret->rc = MENU_ABORTED;
do_check_pop_down = True;
break;
default:
do_check_pop_down = True;
break;
}
if (do_check_pop_down == True)
{
/* popping down may destroy the menu via the dynamic
* popdown action! */
if (!MR_IS_TEAR_OFF_MENU(pmp->menu) &&
fWasAlreadyPopped == False)
{
pop_menu_down(&pmp->menu, pmp);
}
}
XFlush(dpy);
if (!pmp->flags.is_submenu && x_start >= 0 && y_start >= 0 &&
pmret->flags.is_key_press && pmret->rc != MENU_TEAR_OFF)
{
/* 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 */
FWarpPointer(
dpy, 0, Scr.Root, 0, 0, Scr.MyDisplayWidth,
Scr.MyDisplayHeight, x_start, y_start);
if ((*pmp->pexc)->x.elast->type == KeyPress)
{
XEvent e = *(*pmp->pexc)->x.elast;
e.xkey.x_root = x_start;
e.xkey.y_root = y_start;
fev_fake_event(&e);
}
}
if (pmret->rc == MENU_TEAR_OFF)
{
pmret->rc = MENU_SUBMENU_TORN_OFF;
}
dkp.timestamp = 0;
if (is_pointer_grabbed)
{
UngrabEm(GRAB_MENU);
WaitForButtonsUp(True);
is_pointer_ungrabbed = True;
}
if (!pmp->flags.is_submenu)
{
if (pmret->rc == MENU_DONE)
{
if (pmp->ret_paction && *pmp->ret_paction)
{
indirect_depth++;
/* Execute the action */
__menu_execute_function(
pmp->pexc, *pmp->ret_paction);
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;
}
Bool menu_expose(XEvent *event, FvwmWindow *fw)
{
MenuRoot *mr = NULL;
if ((XFindContext(
dpy, event->xany.window, MenuContext, (caddr_t *)&mr) !=
XCNOENT))
{
flush_accumulate_expose(event->xany.window, event);
paint_menu(mr, event, fw);
return True;
}
else
{
return False;
}
}
/*
*
* Procedure:
* update_transparent_menu_bg - set the background of the menu to
* match a forseen move of a menu. If the background is updated
* for the target position before the move is done, and repainted
* after the move, the move will look more seamless.
*
* This method should be folleowd by a call to repaint_transparent_menu
* with the same step_x, stey_y, end_x and any_y, with is_bg_set True
*/
void update_transparent_menu_bg(
MenuRepaintTransparentParameters *prtm,
int current_x, int current_y, int step_x, int step_y,
int end_x, int end_y)
{
MenuRoot *mr;
MenuStyle *ms;
Bool last = False;
mr = prtm->mr;
ms = MR_STYLE(mr);
if (step_x == end_x && step_y == end_y)
{
last = True;
}
if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
{
/* too slow ... */
return;
}
SetWindowBackgroundWithOffset(
dpy, MR_WINDOW(mr), step_x - current_x, step_y - current_y,
MR_WIDTH(mr), MR_HEIGHT(mr),
&Colorset[ST_CSET_MENU(ms)], Pdepth,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
}
/*
*
* Procedure:
* repaint_transparent_menu - repaint the menu background if it is
* tranparent during an animated move. Called in move_resize.c
* (AnimatedMoveAnyWindow). Performance improvement Welcome!
* ideas: - write the foreground into a pixmap and a mask the first time
* this function is called. Then use these pixmaps to draw
* the items
* - Use a Buffer if !IS_TRANSPARENT_PR_PURE and if we do not have one
* already
*/
void repaint_transparent_menu(
MenuRepaintTransparentParameters *prtm,
Bool first, int x, int y, int end_x, int end_y, Bool is_bg_set)
{
MenuItem *mi;
MenuRoot *mr;
MenuStyle *ms;
int h = 0;
int s_h = 0;
int e_h = 0;
Bool last = False;
mr = prtm->mr;
ms = MR_STYLE(mr);
if (x == end_x && y == end_y)
{
last = True;
}
if (!last && CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
{
/* too slow ... */
return;
}
if (!is_bg_set)
{
SetWindowBackground(
dpy, MR_WINDOW(mr), MR_WIDTH(mr), MR_HEIGHT(mr),
&Colorset[ST_CSET_MENU(ms)], Pdepth,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)), False);
}
/* redraw the background of non active item */
for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
{
if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr))
{
int left;
left = MR_HILIGHT_X_OFFSET(mr) - MR_ITEM_X_OFFSET(mr);
if (left > 0)
{
XClearArea(
dpy, MR_WINDOW(mr),
MR_ITEM_X_OFFSET(mr), MI_Y_OFFSET(mi),
left, MI_HEIGHT(mi) +
MST_RELIEF_THICKNESS(mr), 0);
}
h = MI_HEIGHT(mi);
continue;
}
if (h == 0)
{
s_h += MI_HEIGHT(mi);
}
else
{
e_h += MI_HEIGHT(mi);
}
}
XClearArea(
dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr), MST_BORDER_WIDTH(mr),
MR_ITEM_WIDTH(mr), s_h, 0);
if (e_h != 0)
{
XClearArea(
dpy, MR_WINDOW(mr), MR_ITEM_X_OFFSET(mr),
s_h + h + MST_RELIEF_THICKNESS(mr) +
MST_BORDER_WIDTH(mr), MR_ITEM_WIDTH(mr), e_h, 0);
}
/* now redraw the items */
for (mi = MR_FIRST_ITEM(mr); mi != NULL; mi = MI_NEXT_ITEM(mi))
{
MenuPaintItemParameters mpip;
if (mi == MR_SELECTED_ITEM(mr) && MST_DO_HILIGHT_BACK(mr) &&
!CSET_IS_TRANSPARENT_PR_TINT(ST_CSET_MENU(ms)))
{
continue;
}
get_menu_paint_item_parameters(
&mpip, mr, NULL, prtm->fw, NULL, True);
mpip.flags.is_first_item = (MR_FIRST_ITEM(mr) == mi);
menuitem_paint(mi, &mpip);
}
/* if we have a side pic and no side colors we shound repaint the side
* pic */
if ((MR_SIDEPIC(mr) || MST_SIDEPIC(mr)) &&
!MR_HAS_SIDECOLOR(mr) && !MST_HAS_SIDE_COLOR(mr))
{
FvwmPicture *sidePic;
if (MR_SIDEPIC(mr))
{
sidePic = MR_SIDEPIC(mr);
}
else if (MST_SIDEPIC(mr))
{
sidePic = MST_SIDEPIC(mr);
}
else
{
return;
}
XClearArea(
dpy, MR_WINDOW(mr), MR_SIDEPIC_X_OFFSET(mr),
MST_BORDER_WIDTH(mr), sidePic->width,
MR_HEIGHT(mr) - 2 * MST_BORDER_WIDTH(mr), 0);
paint_side_pic(mr, NULL);
}
}
Bool DestroyMenu(MenuRoot *mr, Bool do_recreate, Bool is_command_request)
{
MenuItem *mi,*tmp2;
MenuRoot *tmp, *prev;
Bool in_list = True;
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 */
in_list = 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 (in_list && 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);
menuitem_free(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 (in_list)
{
if (prev == NULL)
{
Menus.all = MR_NEXT_MENU(mr);
}
else
{
MR_NEXT_MENU(prev) = MR_NEXT_MENU(mr);
}
}
/* destroy the window and the display */
if (MR_WINDOW(mr) != None)
{
XDeleteContext(dpy, MR_WINDOW(mr), MenuContext);
XFlush(dpy);
XDestroyWindow(MR_CREATE_DPY(mr), MR_WINDOW(mr));
MR_WINDOW(mr) = None;
XFlush(MR_CREATE_DPY(mr));
}
if (MR_CREATE_DPY(mr) != NULL && MR_CREATE_DPY(mr) != dpy)
{
XCloseDisplay(MR_CREATE_DPY(mr));
MR_CREATE_DPY(mr) = NULL;
}
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))
{
PDestroyFvwmPicture(dpy, MR_SIDEPIC(mr));
}
memset(mr->s, 0, sizeof(*(mr->s)));
free(mr->s);
}
memset(mr->d, 0, sizeof(*(mr->d)));
free(mr->d);
memset(mr, 0, sizeof(*mr));
free(mr);
return True;
}
/* 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,
Bool is_continuation_item)
{
MenuItem *tmp;
char *start;
char *end;
char *token = NULL;
char *option = NULL;
int i;
int is_empty;
Bool do_replace_title;
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_create();
if (MR_FIRST_ITEM(mr) != NULL && StrEquals(token, "title") &&
option != NULL && StrEquals(option, "top"))
{
do_replace_title = True;
}
else
{
do_replace_title = False;
}
append_item_to_menu(mr, tmp, do_replace_title);
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)
{
string_def_t item_pixmaps[] = {
{'*', __scan_for_pixmap},
{'%', __scan_for_pixmap},
{'\0', NULL}};
string_context_t ctx;
SCTX_SET_MI(ctx,tmp);
scanForStrings(
MI_LABEL(tmp)[i], item_pixmaps, &ctx);
}
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
*/
if (is_continuation_item)
{
MI_IS_CONTINUATION(tmp) = True;
}
find_func_t(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;
}
is_empty = (!MI_HAS_TEXT(tmp) && !MI_HAS_PICTURE(tmp));
if (is_empty)
{
if (MI_FUNC_TYPE(tmp) == F_TEARMENUOFF)
{
MI_IS_TEAR_OFF_BAR(tmp) = 1;
MI_IS_SEPARATOR(tmp) = 0;
}
else
{
MI_IS_TEAR_OFF_BAR(tmp) = 0;
MI_IS_SEPARATOR(tmp) = 1;
}
}
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_TEAR_OFF_BAR(tmp)) && !MI_IS_TITLE(tmp));
/*
* misc stuff
*/
MR_ITEMS(mr)++;
MR_IS_UPDATED(mr) = 1;
return;
}
/*
*
* Procedure:
* NewMenuRoot - create a new menu root
*
* Returned Value:
* (MenuRoot *)
*
* Inputs:
* name - the name of the menu root
*
*/
MenuRoot *NewMenuRoot(char *name)
{
MenuRoot *mr;
string_def_t root_strings[] = {
{'@', __scan_for_pixmap},
{'^', __scan_for_color},
{'\0', NULL}};
string_context_t ctx;
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;
SCTX_SET_MR(ctx,mr);
MR_HAS_SIDECOLOR(mr) = False;
scanForStrings(
MR_NAME(mr), root_strings, &ctx);
MR_STYLE(mr) = menustyle_get_default_style();
MR_ORIGINAL_MENU(mr) = mr;
MR_COPIES(mr) = 1;
MR_IS_UPDATED(mr) = 1;
Menus.all = mr;
return mr;
}
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);
}
}
return;
}
void UpdateAllMenuStyles(void)
{
MenuStyle *ms;
for (ms = menustyle_get_default_style(); ms; ms = ST_NEXT_STYLE(ms))
{
menustyle_update(ms);
}
return;
}
void UpdateMenuColorset(int cset)
{
MenuStyle *ms;
FvwmWindow *t;
for (ms = menustyle_get_default_style(); 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) ||
(ST_HAS_TITLE_CSET(ms) && ST_CSET_TITLE(ms) == cset))
{
menustyle_update(ms);
}
}
for (t = Scr.FvwmRoot.next; t != NULL; t = t->next)
{
MenuRoot *mr = NULL;
MenuStyle *ms = NULL;
if (IS_TEAR_OFF_MENU(t) &&
XFindContext(dpy, FW_W(t), MenuContext, (caddr_t *)&mr) !=
XCNOENT &&
(ms = MR_STYLE(mr)))
{
if (ST_HAS_MENU_CSET(ms) &&
ST_CSET_MENU(ms) == cset)
{
SetWindowBackground(
dpy, MR_WINDOW(mr), MR_WIDTH(mr),
MR_HEIGHT(mr),
&Colorset[ST_CSET_MENU(ms)],
Pdepth,
FORE_GC(MST_MENU_INACTIVE_GCS(mr)),
True);
}
else if ((ST_HAS_ACTIVE_CSET(ms) &&
ST_CSET_ACTIVE(ms) == cset) ||
(ST_HAS_GREYED_CSET(ms) &&
ST_CSET_GREYED(ms) == cset))
{
paint_menu(mr, NULL, NULL);
}
}
}
return;
}
/* This is called by the window list function */
void change_mr_menu_style(MenuRoot *mr, char *stylename)
{
MenuStyle *ms = NULL;
ms = menustyle_find(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;
}
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, False);
if (item)
{
free(item);
}
return;
}
/*
* 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 *fw, XEvent *e, MenuRoot *mr,
MenuItem *mi, MenuOptions *pops)
{
char *tok = NULL;
char *naction = action;
char *taction;
int x;
int y;
int button;
int gflags;
int width;
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;
Bool once_more = True;
Bool is_icon_context;
rectangle icon_g;
/* 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 && once_more)
{
/* ^ 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;
}
/* set to False for absolute hints! */
pops->pos_hints.is_relative = True;
fUseItemOffset = False;
fHasContext = True;
fRectangleContext = False;
is_icon_context = False;
if (StrEquals(tok, "context"))
{
if (mi && mr)
{
context_window = MR_WINDOW(mr);
}
else if (fw)
{
if (IS_ICONIFIED(fw))
{
is_icon_context = True;
get_icon_geometry(fw, &icon_g);
context_window = None;
}
else
{
context_window =
FW_W_FRAME(fw);
}
}
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 (fw && IS_ICONIFIED(fw))
{
is_icon_context = True;
get_icon_geometry(fw, &icon_g);
context_window = None;
}
}
else if (StrEquals(tok,"window"))
{
if (fw && !IS_ICONIFIED(fw))
{
context_window = FW_W_FRAME(fw);
}
}
else if (StrEquals(tok,"interior"))
{
if (fw && !IS_ICONIFIED(fw))
{
context_window = FW_W(fw);
}
}
else if (StrEquals(tok,"title"))
{
if (fw)
{
if (IS_ICONIFIED(fw))
{
context_window =
FW_W_ICON_TITLE(fw);
}
else
{
context_window = FW_W_TITLE(fw);
}
}
}
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 (fw && !IS_ICONIFIED(fw))
{
button = BUTTON_INDEX(button);
context_window = FW_W_BUTTON(fw, 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,
(unsigned int*)&width,
(unsigned int*)&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)
{
/* the parent menu */
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 (!is_icon_context &&
(!fHasContext || !context_window ||
!XGetGeometry(
dpy, context_window, &JunkRoot, &JunkX,
&JunkY, (unsigned int*)&width,
(unsigned int*)&height,
(unsigned int*)&JunkBW,
(unsigned int*)&JunkDepth) ||
!XTranslateCoordinates(
dpy, context_window, Scr.Root, 0, 0, &x, &y,
&JunkChild)))
{
/* no window or could not get geometry */
if (FQueryPointer(
dpy,Scr.Root, &JunkRoot, &JunkChild, &x,
&y, &JunkX, &JunkY, &JunkMask) == False)
{
/* pointer is on a different screen - that's
* okay here */
x = 0;
y = 0;
}
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
{
if (is_icon_context)
{
x = icon_g.x;
y = icon_g.y;
width = icon_g.width;
height = icon_g.height;
}
/* 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 (!fev_get_evpos_or_query(
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 */
once_more = False;
} /* while */
if (is_action_empty)
{
if (!pops->pos_hints.has_screen_origin)
{
pops->pos_hints.has_screen_origin = 1;
if (!fev_get_evpos_or_query(
dpy, Scr.Root, 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 if (StrEquals(tok, "TearOffImmediately"))
{
pops->flags.do_tear_off_immediately = 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;
}
/* ---------------------------- new menu loop code ------------------------- */
#if 0
/*!!!*/
typedef enum
{
MTR_XEVENT = 0x1,
MTR_FAKE_ENTER_ITEM = 0x2,
MTR_PROPAGATE_XEVENT_UP = 0x4,
MTR_PROPAGATE_XEVENT_DOWN = 0x8,
MTR_POPUP_TIMEOUT = 0x10,
MTR_POPDOWN_TIMEOUT = 0x20
} mloop_trigger_type_t;
typedef_struct
{
mloop_trigger_type_t type;
XEvent trigger_ev;
int ticks_passed;
} mloop_trigger_t;
typedef_struct
{
int popup_10ms;
int popdown_10ms;
} mloop_timeouts_t;
typedef struct
{
MenuRoot *current_menu;
XEvent *in_ev;
struct
{
unsigned do_fake_enter_item : 1;
unsigned do_propagete_event_up : 1;
unsigned do_propagete_event_down : 1;
} flags;
} mloop_get_trigger_input_t;
/*!!!static*/
mloop_trigger_type_t __mloop_get_trigger(
mloop_trigger_t *ret_trigger, mloop_timeouts_t *io_timeouts,
const mloop_get_trigger_input_t * const in,
{
if (in_out->in_flags->do_propagate_event_down)
{
return MTR_PROPAGATE_XEVENT_DOWN;
}
else if (in_out->in_flags->do_propagate_event_up)
{
if (a != b)
{
return MTR_PROPAGATE_XEVENT_UP;
}
else
{
}
/*!!!return propagate up*/
/*!!!*/
}
/*!!!read event or wait for timeout*/
while (0/*!!!not finished*/)
{
/*!!!rc = 0*/
if (0/*!!!wait for tiomeout*/)
{
/*!!!check for event*/
}
else
{
/*!!!block for event*/
}
if (0/*got event*/)
{
/*!!!rc = MTR_XEVENT*/
}
if (0/*!!!popup timed out;break*/)
{
/*!!!rc = MTR_POPUP;break*/
}
if (0/*!!!popdown timed out;break*/)
{
/*!!!rc = MTR_POPDOWN;break*/
}
/*!!!sleep*/
}
if (0/*!!!rc == MTR_XEVENT && evtype == MotionNotify*/)
{
/*!!!eat up further MotionNotify events*/
}
return 0/*!!!rc*/;
}
/*!!!static*/
void __menu_loop_new(
MenuParameters *pmp, MenuReturn *pmret, double_keypress *pdkp)
{
mloop_evh_input_t mei;
#if 0
mloop_ret_code_t mloop_ret;
#endif
mloop_evh_data_t med;
mloop_static_info_t msi;
MenuOptions mops;
Bool is_finished;
/*!!!init menu loop*/
__mloop_init(pmp, pmret, &mei, &med, &msi, &mops);
for (is_finished = False; !is_finished; )
{
mloop_trigger_type_t mtr;
mtr = __mloop_get_trigger(
pmp, pmret, &mei, &med, &msi);
switch (mtr)
{
case MTR_XEVENT:
/*!!!handle event*/
break;
case MTR_FAKE_ENTER_ITEM:
/*!!!fake enter item*/
break;
case MTR_PROPAGATE_XEVENT_UP:
/*!!!handle propagation*/
break;
case MTR_PROPAGATE_XEVENT_DOWN:
/*!!!handle propagation*/
break;
case MTR_POPUP_TIMEOUT:
/*!!!handle popup*/
break;
case MTR_POPDOWN_TIMEOUT:
/*!!!handle popdown*/
break;
}
#if 0
mloop_ret = __mloop_handle_event(
pmp, pmret, pdkp, &mei, &med, &msi);
switch (mloop_ret)
{
case MENU_MLOOP_RET_LOOP:
continue;
case MENU_MLOOP_RET_END:
is_finished = True;
break;
default:
break;
}
/* Now handle new menu items, whether it is from a
* keypress or a pointer motion event. */
if (med.mi != NULL)
{
mloop_ret = __mloop_handle_action_with_mi(
pmp, pmret, pdkp, &mei, &med, &msi,
&mops);
}
else
{
mloop_ret = __mloop_handle_action_without_mi(
pmp, pmret, pdkp, &mei, &med, &msi,
&mops);
}
if (mloop_ret == MENU_MLOOP_RET_END)
{
is_finished = True;
}
XFlush(dpy);
#endif
}
__mloop_exit(pmp, pmret, pdkp, &mei, &med, &msi, &mops);
return;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1