/* $Id: Menu.c,v 1.6 1997/06/27 12:41:13 archer Exp $
 *	MENU.C - Handle Menu objects
 *
 * The only children of a Menu allowed are MenuItems objects.
 */
#include <Widget/MenuP.h>
#include <Widget/EventsP.h>
#include <Widget/MenuItemP.h>
#include <Widget/PopupP.h>
#include <Widget/drawP.h>
#include <Widget/UnWMP.h>
#include <X11/cursorfont.h>


/**
 ** No more than 16 successive menus
 **/
#ifndef MAX_MENU_DEPTH
#define MAX_MENU_DEPTH	16
#endif


/**
 ** External variable
 **/
char	x11__menu_class[] = "Menu";


/**
 ** Private variables
 **/
static X11_widget	popped_menus[MAX_MENU_DEPTH];
static X11_widget	inside_item, true_inside,
			acted_item;
static int		popped_x[MAX_MENU_DEPTH], popped_y[MAX_MENU_DEPTH];
static int		menu_depth;


/**
 ** Internal: MENU_POPPED
 **	Add a new level to popped/active menus
 **/
static void menu_popped(X11_widget wdg)
{
/*
 * This should never occurs, unless you're very bad at designing interfaces!
 */
	if (menu_depth >= MAX_MENU_DEPTH)
		abort();
/*
 * Keep track of ourselves
 */
	popped_menus[menu_depth] = wdg;
	X11_WidgetRootLocation(wdg, popped_x + menu_depth,
					 popped_y + menu_depth);
	menu_depth++;
	inside_item = 0;
}


/**
 ** Internal: MENU_UNMAPS
 **	The menu is no longer popped down
 **/
static void menu_unmaps(X11_widget wdg)
{
/*
 * First of all, menu is no longer on screen
 */
	XUnmapWindow(x11__screen, wdg->id);
	wdg->unmapped = True;
/*
 * The ancestor of this? If it's a menuitem, then, suddendly, we're "leaving"
 * it (as in: it shall no longer have the menu popped up)
 */
	wdg = wdg->parent;
	if (strcmp(wdg->class, x11__menuitem_class) == 0)
		(*wdg->on_exit_window)(wdg, (XEvent *)0);
}


/**
 ** Internal: MENU_MAPS
 **	The selected menu appears automagically at the right place on the
 **	screen
 **/
static void menu_maps(X11_widget wdg)
{
X11_widget	popper;
int		x, y;
int		v;
/*
 * If popup requested, then do it...
 */
	if (W_MENU->on_popup)
		(*W_MENU->on_popup)(wdg, W_MENU->user_data);
/*
 * Recompute the geometry of the menu
 */
	(*wdg->on_geometry_check)(wdg, (XEvent *)0);
/*
 * Menu usually maps relative to their parent
 */
	popper = wdg->parent;
	X11_WidgetRootLocation(popper, &x, &y);
/*
 * The hierarchy is MenuBar.MenuItem.Menu
 */
	if (strcmp(popper->class, x11__menuitem_class) == 0) {
		if (strcmp(popper->parent->class, x11__menu_class))
			y += popper->h;
/*
 * The hierarchy is Menu.MenuItem.Menu
 */
		else {
			x += popper->w;
			y -= wdg->shadow_thickness;
		}
/*
 * The hierarchy is MenuButton.Menu
 */
	} else {
/*
 * If a label exists, the menu appears centered relative to the label
 */
		if (W_LABEL->string && W_LABEL->string[0]) {
			v  = (wdg->w - W_LABEL->w)/2 -
			     (popper->w - ((x11__label *)popper)->w)/2;
			x -= v;
/*** HACK ***/ /* y seems ok, but x varies? */
		}
	}
/*
 * Make sure the menu appears on the screen!
 */
	v = x11__widget_root->w - wdg->w;
	if (x > v)
		x = v;
	v = x11__widget_root->h - wdg->h;
	if (y > v)
		y = v;
	if (x < 0)
		x = 0;
	if (y < 0)
		y = 0;
/*
 * Final position is:
 */
	wdg->x = x;
	wdg->y = y;
	XMoveWindow(x11__screen, wdg->id, x, y);
	XRaiseWindow(x11__screen, wdg->id);
	XMapWindow(x11__screen, wdg->id);
	wdg->unmapped = False;
}


/**
 ** X11_MENU_POP
 **	Pop a new level of sub-menus
 **/
void x11__menu_pop(X11_widget wdg)
{
	menu_maps(wdg);
	menu_popped(wdg);
}


/**
 ** Internal: MOTION_IN_MENU
 **	Is the cursor now inside one menu, or it's children?
 **/
static boolean motion_in_menu(int i, int x, int y)
{
X11_widget	menu;
X11_widget	item;
int		b2;
/*
 * Where am I?
 */
	menu = popped_menus[i];
	x   -= popped_x[i];
	y   -= popped_y[i];
/*
 * Check if outside or on the border
 */
	if (x < 0 || y < 0 || x >= menu->w || y >= menu->h)
		return False;
/*
 * Now, check in which children I am
 */
	b2 = menu->border2 / 2;
	x -= b2;
	y -= b2;
	for (item = menu->child; item; item = item->sibling) {
		if (item->unmanaged || item->unmapped)
			continue;
/*
 * Check inside item?
 */
		if (x >= item->x &&
		    y >= item->y &&
		    x < (item->x + item->w) &&
		    y < (item->y + item->h))
			break;
	}
/*
 * Spurious move?
 */
	if (item) {
		true_inside = item;
		if (item == inside_item)
			return True;
	}
/*
 * We're inside the contents of the menu
 * First, pop down any further menus
 */
	i++;
	if (menu_depth > i) {
/*
 * Dont pop down the menu we're likely to reopen just afterward!
 */
		if (item != 0 && item->child == popped_menus[i]) {
			i++;
			item = 0;
		}
/*
 * Pop all these menus
 */
		while (menu_depth > i)
			menu_unmaps(popped_menus[--menu_depth]);
	}
/*
 * Do we left some item?
 */
	if (inside_item && inside_item != item && inside_item->on_exit_window)
		(*inside_item->on_exit_window)(inside_item, (XEvent *)0);
/*
 * We're now inside that item... If it is a MENUITEM!
 */
	if (item && strcmp(item->class, x11__menuitem_class))
		item = 0;
	inside_item = item;
/*
 * In a Menuitem?
 */
	if (item) {
		if (item->on_enter_window)
			(*item->on_enter_window)(item, (XEvent *)0);
/*
 * Make non default
 */
#if 0
		if (menu == popped_menus[menu_depth-1] && menu->can_default) {
			menu->is_default = False;
			for (item = menu->child; item; item = item->sibling)
				if (item->is_default) {
					item->is_default = False;
					XClearArea(x11__screen, item->id,
							0, 0, 0, 0, True);
				}
		} else
			menu_default(popped_menus[menu_depth-1], True);
#endif
	}
/*
 * We were inside the pop
 */
	return True;
}


/**
 ** Internal: MOTION_OCCURS
 **	Handle the motion of the pointer
 **/
static void motion_occurs(int x, int y)
{
int	i;
/*
 * Check all popped in turn (Last popped, first checked)
 */
	for (i = menu_depth - 1; i >= 0; i--) {
		if (motion_in_menu(i, x, y))
			return;
	}
/*
 * Cursor is wandering in no particular menu?
 */
	if (inside_item && inside_item->on_exit_window)
		(*inside_item->on_exit_window)(inside_item, (XEvent *)0);
	true_inside = 0;
	inside_item = 0;
}


/**
 ** Internal: EVENT_LOOP
 **	Handle all events, while in menu mode
 **/
static boolean event_loop(void)
{
XEvent	e;
/*
 * Parse one event
 */
	x11__wait_event(&e);
	switch (e.type) {
/*
 * Press of the button (ignore, we're using it as a movement)
 */
	    case ButtonPress:
		motion_occurs(e.xbutton.x_root, e.xbutton.y_root);
		break;
/*
 * Release of the button
 */
	    case ButtonRelease:
		motion_occurs(e.xbutton.x_root, e.xbutton.y_root);
/*
 * Pop down menus. The last menu is popped down only if needed
 */
		if ((acted_item = inside_item) != 0 &&
		    acted_item->on_special_event)
			(*acted_item->on_special_event)(acted_item, 0);
		while (menu_depth > 1)
			menu_unmaps(popped_menus[--menu_depth]);
		menu_depth = 0;
		return False;
/*
 * Shortcuts?
 */
	    case KeyPress:
	    case KeyRelease:
		break;
/*
 * Motion occurs...
 */
	    case MotionNotify:
		motion_occurs(e.xmotion.x_root, e.xmotion.y_root);
		break;
/*
 * All other events are dispatched
 */
	    default:
		x11__event_dispatch(&e);
		break;
	}
/*
 * Next event, please
 */
	return True;
}


/**
 ** X11_MENU_OPEN
 **	Open the first menu. The menu becomes mapped, if it wasn't.
 **/
void x11__menu_open(X11_widget wdg, XEvent *e)
{
boolean	had_to_map;
/*
 * Map the menu, if needed (we need the window to be mapped to grab)
 */
	if (wdg->unmapped) {
		menu_maps(wdg);
		had_to_map = True;
	} else
		had_to_map = False;
/*
 * Grab the screen on behalf of popped menu
 */
	XGrabPointer(x11__screen, wdg->id, True,
				(ButtonPressMask|ButtonReleaseMask|
				 PointerMotionMask),
				GrabModeAsync, GrabModeAsync,
				None, None, CurrentTime);
	XGrabKeyboard(x11__screen, wdg->id, False,
				GrabModeAsync, GrabModeAsync, CurrentTime);
/*
 * We're at 0 depth
 */
	menu_depth = 0;
	acted_item = 0;
	menu_popped(wdg);
/*
 * Grabbed loop
 */
	if (e)
		motion_occurs(e->xmotion.x_root, e->xmotion.y_root);
	while (event_loop())
		;
/*
 * Ungrab at the end
 */
	XUngrabPointer(x11__screen, CurrentTime);
	XUngrabKeyboard(x11__screen, CurrentTime);
	XFlush(x11__screen);
	if (acted_item &&
	    !acted_item->is_disabled &&
	    acted_item->on_button_release)
		(*acted_item->on_button_release)(acted_item, (XEvent *)0);
/*
 * Unmap the menu, if it was not mapped
 */
	if (had_to_map) {
		wdg->unmapped = True;
		XUnmapWindow(x11__screen, wdg->id);
	}
}


/**
 ** Internal: MENU_EXPOSE
 **	Display border around menu
 **/
static void menu_expose(X11_widget wdg, XEvent *dummy)
{
/*
 * Menu draws here
 */
	x11__draw_start(wdg);
	x11__shadows_gc();
	x11__draw_upshadow(wdg->shadow_thickness);
/*
 * Menu title?
 */
	if (W_LABEL->string && W_LABEL->string[0]) {
		x11__y_top += 2;
		x11__y_h    = 0;
		x11__label_draw(W_LABEL, wdg->background, wdg->foreground);
/*
 * Restore position
 */
		x11__y_top   = x11__y_bot + 3;
		x11__x_left  = wdg->shadow_thickness;
		x11__x_right = wdg->w - x11__x_left;
		x11__x_w     = x11__x_right-- - x11__x_left;
/*
 * Draw separator
 */
		XDrawLine(x11__screen, x11__cur_id, x11__topleft_gc,
					x11__x_left, x11__y_top,
					x11__x_right, x11__y_top);
		x11__y_top++;
		XSetForeground(x11__screen, x11__active_gc, wdg->active_bk);
		XDrawLine(x11__screen, x11__cur_id, x11__active_gc,
					x11__x_left, x11__y_top,
					x11__x_right, x11__y_top);
		x11__y_top++;
		XDrawLine(x11__screen, x11__cur_id, x11__bottomright_gc,
					x11__x_left, x11__y_top,
					x11__x_right, x11__y_top);
	}
}


/**
 ** Internal: MENU_GEOMETRY
 **	Compute the new menu geometry, then resize
 **/
static void menu_geometry(X11_widget wdg, XEvent *dummy)
{
X11_widget	child;
unsigned int	w, h;
int		left_m, right_m, x;
int		v;
/*
 * Menu might have a label
 */
	x = wdg->shadow_thickness + wdg->margin_inside;
	h = x;
	if (W_LABEL->string && W_LABEL->string[0])
		h += 9 + W_LABEL->h;
/*
 * First loop determines MenuItem's sizes and margins. It also places the
 * items at their final places (this, we know at once)
 */
	left_m  = 0;
	right_m = 0;
	for (child = wdg->child; child; child = child->sibling) {
		if (child->unmanaged)
			continue;
/*
 * Child exists
 */
		(*child->on_geometry_check)(child, (XEvent *)0);
		if (child->x != x || child->y != h) {
			child->x = x;
			child->y = h;
			XMoveWindow(x11__screen, child->id, x, h);
		}
/*
 * Vertical size is simple
 */
		h += child->h_min;
/*
 * Margins in MenuItems
 */
		if (strcmp(child->class, x11__menuitem_class))
			continue;
		if (C_MENUBUTTON->left_margin > left_m)
			left_m = C_MENUBUTTON->left_margin;
		if (C_MENUBUTTON->right_margin > right_m)
			right_m = C_MENUBUTTON->right_margin;
	}
/*
 * All items should share the same margins (left and right). With these
 * margins, we'll know the width...
 */
	if (left_m > right_m)
		right_m = left_m;
/*
 * Minimum size is the label's size
 */
	if (W_LABEL->string && W_LABEL->string[0])
		w = 4 + W_LABEL->w;
	else
		w = 0;
	for (child = wdg->child; child; child = child->sibling) {
		if (child->unmanaged)
			continue;
		v = child->w_min;
/*
 * Adjust margins
 */
		if (strcmp(child->class, x11__menuitem_class) == 0) {
			if (C_MENUBUTTON->left_margin < left_m) {
				v += left_m - C_MENUBUTTON->left_margin;
				C_MENUBUTTON->left_margin = left_m;
			}
			if (C_MENUBUTTON->right_margin < right_m) {
				v += right_m - C_MENUBUTTON->right_margin;
				C_MENUBUTTON->right_margin = right_m;
			}
		}
/*
 * Actual size needed
 */
		if (v > w)
			w = v;
	}
/*
 * Size of this menu
 */
	w += x*2 + wdg->border2;
	h += x + wdg->border2;
	if (!w)
		w = 1;
	if (!h)
		h = 1;
	if (wdg->w != w || wdg->h != h) {
		wdg->w = w;
		wdg->h = h;
		x11__core_resize(wdg, (XEvent *)0);
	}
	w -= x*2 + wdg->border2;
/*
 * Resize all childrens
 */
	for (child = wdg->child; child; child = child->sibling) {
		if (child->unmanaged)
			continue;
/*
 * Resize needed?
 */
		if (w != child->w || child->h_min != child->h) {
			child->w = w;
			child->h = child->h_min;
			(*child->on_resize_window)(child, (XEvent *)0);
		}
	}
}


/**
 ** X11_MENU_BY_INDEX
 **	Return the option in a menu
 **/
X11_widget X11_MenuByIndex(X11_widget wdg, int number)
{
X11_widget	c;
/*
 * Simple
 */
	c = wdg->child;
	while (number-- > 0) {
		c = c->sibling;
		if (!c)
			break;
	}
	return c;
}


/**
 ** X11_MENU_EMPTY
 **	Empty the menu of its options
 **/
void X11_MenuEmpty(X11_widget wdg)
{
/*
 * Simple loop
 */
	while (wdg->child)
		X11_Destroy(wdg->child);
	wdg->is_default  = False;
	wdg->can_default = False;
}


/**
 ** X11_MENU_TRUNCATE
 **	Empty the menu of all options starting with that one. Others remain.
 **/
void X11_MenuTruncate(X11_widget menu, X11_widget start)
{
X11_widget	child;
X11_widget	prev;
/*
 * Degenerate case
 */
	if (start == (child = menu->child)) {
		X11_MenuEmpty(menu);
		return;
	}
/*
 * Defaulting
 */
	menu->is_default  = False;
	menu->can_default = False;
	for (prev = 0; child; child = child->sibling)
		if (start == child) {
			while (prev->sibling)
				X11_Destroy(prev->sibling);
			break;
		} else {
			if (child->can_default)
				menu->can_default = True;
			prev = child;
		}
}


/**
 ** X11_MENU_ON_POP_UP
 **	On the popping of a menu, calls the specific function
 **/
void X11_MenuOnPopup(X11_widget wdg, X11_callback call, void *data)
{
	W_MENU->on_popup  = call;
	W_MENU->user_data = data;
}


/**
 ** X11_MENU_TITLE
 **	Set a menu title. May use the special value COPY_FROM_PARENT
 **	(all menu parents are subclassed from label...)
 **/
void X11_MenuTitle(X11_widget wdg, char *title)
{
/*
 * Inherit? (you clear the title by setting it to "")
 */
	if (title == (char *)0)
		title = ((x11__label *)(wdg->parent))->string;
/*
 * Set new value
 */
	if (W_LABEL->string)
		free(W_LABEL->string);
	if (title[0])
		W_LABEL->string = strdup(title);
	else
		W_LABEL->string = 0;
	x11__label_size(W_LABEL);
}


/**
 ** X11_MENU
 **	Create a menu window
 **/
X11_widget X11_Menu(X11_widget parent, char *name)
{
x11__menu	*object;
/*
 * Get resource
 */
	object = allocator(x11__menu);
	if (!name)
		name = "menu";
	x11__core_setup(&object->label.core, parent, x11__menu_class, name);
/*
 * Initialize fields
 */
	object->label.core.event_mask = PointerMotionMask|PointerMotionHintMask|
					ExposureMask;
	object->label.core.on_destruction    = x11__label_cleanup;
	object->label.core.on_expose         = menu_expose;
	object->label.core.on_geometry_check = menu_geometry;
	object->label.core.on_window_create  = x11__nowm_create;
/*
 * Figure out defaults?
 */
	object->label.core.shadow_thickness = 2;
/*
 * Fetch resources
 */
	x11__label_resources(&object->label, x11__font_helveticabold12, 0);
/*
 * Finished. Note that window is child of root window, and unmapped
 * (but managed)
 */
	object->label.core.unmanaged  = False;
	object->label.core.unmapped   = True;
	object->label.core.use_cursor = XCreateFontCursor(x11__screen, XC_top_left_arrow);
/*
 * Ok
 */
	return OBJECT_P;
}
