/* $Id: Scrollbar.c,v 1.5 1997/06/10 15:29:16 archer Exp $
 *	SCROLLBAR.C - Handle Scrollbar objects
 *
 * The scrollbar is used mainly by the Scroller object
 */
#include <Widget/ScrollbarP.h>
#include <Widget/WidgetsP.h>
#include <Widget/Shell.h>
#include <Widget/drawP.h>
#include <Widget/miscP.h>
#include <X11/keysym.h>
#include <X11/cursorfont.h>


/****************************************************
 ** LIFT Object (sub-object of the scrollbar only) **
 ****************************************************/
/**
 ** Internal: LIFT_ABSOLUTE_POSITION
 **	Reposition the lift at X, offset by lift-min
 **/
static void lift_absolute_position(x11__scrollbar *wdg, int p)
{
long	v;
int	s;
/*
 * Check bounds
 */
	s = wdg->lift_area_w;
	if (wdg->core.fixed_h)
		s -= wdg->lift->w;
	else
		s -= wdg->lift->h;
	p -= wdg->lift_area_x;
	if (p < 0)
		p = 0;
	if (p > s)
		p = s;
/*
 * Now, resizes the scrollbar
 */
	v = x11__rescale_long(p, s, wdg->max_w);
	x11__scrollbar_moveto(wdg, v);
}


/**
 ** Internal: LIFT_PRESS
 **	Button depressed on the lift?
 **/
static void lift_press(X11_widget wdg, XEvent *e)
{
/*
 * If we're pressing the very first button, keep the initial x/y of the
 * mouse cursor
 */
	if (!(e->xbutton.state&(Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))){
		wdg->hint_x = e->xbutton.x;
		wdg->hint_y = e->xbutton.y;
	}
	((x11__scrollbar*)wdg->parent)->down_lift = True;
	x11__core_press(wdg, e);
}


/**
 ** Internal: LIFT_RELEASE
 **	Button released from the lift?
 **/
static void lift_release(X11_widget wdg, XEvent *e)
{
	((x11__scrollbar*)wdg->parent)->down_lift = False;
	x11__core_release(wdg, e);
}


/**
 ** Internal: LIFT_MOTION
 **	Follow the lift (we're called by the lift itself!)
 **/
static void lift_motion(X11_widget wdg, XEvent *e)
{
x11__scrollbar	*bar;
int		v;
/*
 * We're moving the scrollbar
 */
	bar = (x11__scrollbar *)wdg->parent;
	if (bar->down_lift) {
		if (bar->core.fixed_h)
			v = wdg->x + (e->xmotion.x - wdg->hint_x);
		else
			v = wdg->y + (e->xmotion.y - wdg->hint_y);
		lift_absolute_position(bar, v);
	}
}


/**
 ** Internal: LIFT_EXPOSE
 **	Redraw the lift. We're lucky, it's orientation-independent
 **/
static void lift_expose(X11_widget wdg, XEvent *dummy)
{
/*
 * Start drawing
 */
	x11__draw_start(wdg);
	x11__shadows_gc();
	if (wdg->is_active) {
		x11__draw_downshadow(wdg->shadow_thickness);
		x11__draw_filled(wdg->active_bk);
	} else {
		x11__draw_upshadow(wdg->shadow_thickness);
		x11__draw_filled(wdg->background);
	}
}


/**
 ** Internal: LIFT_RESIZE
 **	Resize a variable-size lift.
 **/
static void lift_resize(x11__scrollbar *wdg, int width)
{
X11_widget	lift;
int		scale, pos;
int		m;
/*
 * Interpolate size
 */
	lift = wdg->lift;
	scale = x11__rescale_long(wdg->width, wdg->max, wdg->lift_area_w);
	if (lift->fixed_w)
		m = lift->h_min;
	else
		m = lift->w_min;
	if (scale < m)
		scale = m;
/*
 * Interpolate location
 */
	if (wdg->value == 0)
		pos = 0;
	else
		pos = x11__rescale_long(wdg->value, wdg->max_w,
						wdg->lift_area_w - scale);
	pos += wdg->lift_area_x;
	m = wdg->core.focus_thickness + wdg->core.shadow_thickness;
/*
 * Relocate and resize
 */
	if (lift->fixed_w) {
		if (width < 0)
			width = lift->w;
		if (lift->h != scale || lift->w != width) {
			lift->w = width;
			lift->h = scale;
			(*lift->on_resize_window)(lift, (XEvent *)0);
		}
		if (lift->x != m || lift->y != pos) {
			lift->x = m;
			lift->y = pos;
			if (lift->id)
				XMoveWindow(x11__screen, lift->id, m, pos);
		}
	} else {
		if (width < 0)
			width = lift->h;
		if (lift->w != scale || lift->h != width) {
			lift->w = scale;
			lift->h = width;
			(*lift->on_resize_window)(lift, (XEvent *)0);
		}
		if (lift->y != m || lift->x != pos) {
			lift->x = pos;
			lift->y = m;
			if (lift->id)
				XMoveWindow(x11__screen, lift->id, pos, m);
		}
	}
}


/**
 ** Internal: LIFT_CREATE
 **	Create the Lift sub-object
 **/
static X11_widget lift_create(X11_widget wdg)
{
X11_widget	lift;
int		m;
/*
 * Create lift
 */
	lift = allocator(x11__core);
	x11__core_setup(lift, wdg, "Lift", "lift");
/*
 * Basic values
 */
	lift->event_mask        = ButtonPressMask|ButtonReleaseMask|
				  ButtonMotionMask|ExposureMask;
	lift->on_button_press   = lift_press;
	lift->on_button_release = lift_release;
	lift->on_expose         = lift_expose;
	lift->on_motion_notify  = lift_motion;
/*
 * Depending on the style
 */
	lift->shadow_thickness = 2;
	lift->margin_inside    = 8;
	x11__resource_basic(lift);
	if (lift->margin_inside < 1)
		lift->margin_inside = 1;
/*
 * Finish geometry of lift
 */
	m  = lift->border2 + lift->shadow_thickness * 2;
	lift->fixed_w = wdg->fixed_w;
	lift->fixed_h = wdg->fixed_h;
	lift->w_min = lift->w = m + 1;
	lift->h_min = lift->h = m + 1;
	return lift;
}


/******************************************************
 ** SCROLLBAR Object (container for the lift+arrows) **
 ******************************************************/


/**
 ** X11_SCROLLBAR_SET_VALUE
 **	Where is the scrollbar now?
 **/
void x11__scrollbar_set_value(x11__scrollbar *wdg, long value)
{
/*
 * Be sure we're in this
 */
	if (value > wdg->max_w)
		value = wdg->max_w;
	if (value < 0)
		value = 0;
/*
 * Value changed?
 */
	if (value == wdg->value)
		return;
/*
 * Set and redraw
 */
	wdg->value = value;
	lift_resize(wdg, -1);
}


/**
 ** X11_SCROLLBAR_MOVE_TO
 **	Move scrollbar to a new position (scrolled)
 **/
void x11__scrollbar_moveto(x11__scrollbar *wdg, long v)
{
/*
 * Change only if needed
 */
	if (v > wdg->max_w)
		v = wdg->max_w;
	if (v < 0)
		v = 0;
/*
 * User information and correction
 */
	if (v != wdg->value) {
		if (wdg->callback)
			v = (*wdg->callback)(wdg->for_widget, v);
		x11__scrollbar_set_value(wdg, v);
	}
}


/**
 ** Internal: MOVE_MINUS_ONE
 **	Move one line/pixel/whatever toward the beginning
 **/
static void move_minus_one(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value)
		x11__scrollbar_moveto(W_SCROLLBAR,
					W_SCROLLBAR->value-W_SCROLLBAR->arrow);
}


/**
 ** Internal: MOVE_PLUS_ONE
 **	Move one line/pixel/whatever toward the end
 **/
static void move_plus_one(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value < W_SCROLLBAR->max_w)
		x11__scrollbar_moveto(W_SCROLLBAR,
					W_SCROLLBAR->value+W_SCROLLBAR->arrow);
}


/**
 ** Internal: MOVE_PAGE_UP
 **	Move one page upward
 **/
static void move_page_up(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value)
		x11__scrollbar_moveto(W_SCROLLBAR,
				      W_SCROLLBAR->value - W_SCROLLBAR->width);
}


/**
 ** Internal: MOVE_PAGE_DOWN
 **	Move one page downward
 **/
static void move_page_down(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value < W_SCROLLBAR->max_w)
		x11__scrollbar_moveto(W_SCROLLBAR,
				      W_SCROLLBAR->value + W_SCROLLBAR->width);
}


/**
 ** Internal: MOVE_TOP
 **	Move to beginning of scrolling
 **/
static void move_top(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value)
		x11__scrollbar_moveto(W_SCROLLBAR, 0);
}


/**
 ** Internal: MOVE_BOTTOM
 **	Move to end of scrolling
 **/
static void move_bottom(X11_widget dummy, void *wdg)
{
	if (W_SCROLLBAR->value < W_SCROLLBAR->max_w)
		x11__scrollbar_moveto(W_SCROLLBAR, W_SCROLLBAR->max_w);
}


/**
 ** Internal: SCROLLBAR_PRESS
 **	A button was depressed inside the scrollbar
 **/
static void scrollbar_press(X11_widget wdg, XEvent *e)
{
int	p, l;
/*
 * Where?
 */
	if (wdg->fixed_h) {
		p = e->xbutton.x;
		l = W_SCROLLBAR->lift->x;
	} else {
		p = e->xbutton.y;
		l = W_SCROLLBAR->lift->y;
	}
/*
 * Scroll just once
 */
	if (p < l)
		move_page_up(0, (void *)wdg);
	else
		move_page_down(0, (void *)wdg);
}


/**
 ** Internal: SCROLLBAR_EXPOSE
 **	Display a scrollbar
 **/
static void scrollbar_expose(X11_widget wdg, XEvent *dummy)
{
/*
 * Draw is simple
 */
	x11__draw_start(wdg);
	x11__shadows_gc();
	if (wdg->focus_thickness)
		x11__draw_lined(wdg->has_focus ? wdg->focus_color
						:wdg->parent->background,
				wdg->focus_thickness);
	x11__draw_downshadow(wdg->shadow_thickness);
}


/**
 ** Internal: SCROLLBAR_RESIZE
 **	Resize the window, moving arrows around
 **/
static void scrollbar_resize(X11_widget wdg, XEvent *e)
{
X11_widget	minus, plus;
int		w, h;
int		x, y;
int		m;
/*
 * Do the actual resize
 */
	x11__core_resize(wdg, e);
/*
 * Size is orientation independent
 */
	if (wdg->fixed_h) {
		w = wdg->w;
		h = wdg->h;
	} else {
		w = wdg->h;
		h = wdg->w;
	}
	m  = wdg->focus_thickness + wdg->shadow_thickness;
/*
 * W is the variable size, H is the fixed size
 */
	w -= wdg->border2 + m * 2;
	h -= wdg->border2 + m * 2;
/*
 * Remove the part about the upper arrow
 */
	if ((minus = W_SCROLLBAR->minus) != 0) {
		if (minus->w != h || minus->h != h) {
			minus->w = h;
			minus->h = h;
			(*minus->on_resize_window)(minus, e);
		}
		if (minus->x != m || minus->y != m) {
			minus->x = m;
			minus->y = m;
			if (minus->id)
				XMoveWindow(x11__screen, minus->id, m, m);
		}
		w -= h;
	}
/*
 * Remove the part about the lower arrow
 */
	if ((plus = W_SCROLLBAR->plus) != 0) {
		if (plus->w != h || plus->h != h) {
			plus->w = h;
			plus->h = h;
			(*plus->on_resize_window)(plus, e);
		}
		if (wdg->fixed_h) {
			x = wdg->w - wdg->border2 - m - h;
			y = m;
		} else {
			x = m;
			y = wdg->h - wdg->border2 - m - h;
		}
		if (plus->x != x || plus->y != y) {
			plus->x = x;
			plus->y = y;
			if (plus->id)
				XMoveWindow(x11__screen, plus->id, x, y);
		}
		w -= h;
	}
/*
 * Lift characteristics
 */
	if (w <= 0)
		w = 1;	/* Never happens! */
	W_SCROLLBAR->lift_area_w = w;
	W_SCROLLBAR->lift_area_x = m + h;
	lift_resize(W_SCROLLBAR, h);
}


/**
 ** Internal: GEOMETRY
 **	Compute the geometry of the scrolling bar
 **/
static void scrollbar_geometry(X11_widget wdg, XEvent *dummy)
{
X11_widget	child;
int		w, h;
int		m;
/*
 * It can be considered a row
 */
	w = 0;
	h = 0;
	for (child = wdg->child; child; child = child->sibling) {
		(*child->on_geometry_check)(child, dummy);
		if (wdg->fixed_w) {
			if (child->w_min > w)
				w = child->w_min;
			h += child->h_min;
		} else {
			w += child->w_min;
			if (child->h_min > h)
				h = child->h_min;
		}
	}
/*
 * At least room for drawing
 */
	m  = 2 * (wdg->focus_thickness + wdg->shadow_thickness) + wdg->border2;
	if (wdg->fixed_w)
		h++;
	else
		w++;
	wdg->w_min = w + m;
	wdg->h_min = h + m;
	x11__widget_grow(wdg);
/*
 * Make sure the resize was called at least once
 */
	if (W_SCROLLBAR->lift_area_w == 0)
		scrollbar_resize(wdg, (XEvent *)0);
}


/**
 ** X11_SCROLLBAR_SET_BOUNDS
 **	Adjust the bounds for the scrollbar
 **/
void x11__scrollbar_set_bounds(X11_widget wdg, long max, int size)
{
boolean	redo = False;
/*
 * Adjust for default values
 */
	if (max < 0)
		max = W_SCROLLBAR->max;
	if (size < 0)
		size = W_SCROLLBAR->width;
	if (size > max)
		size = max;
/*
 * Has anything changed?
 */
	if (max != W_SCROLLBAR->max) {
		W_SCROLLBAR->max = max;
		redo = True;
	}
	if (size != W_SCROLLBAR->width) {
		W_SCROLLBAR->width = size;
		redo = True;
	}
	max -= size;
	if (max < 0)
		max = 0;
	W_SCROLLBAR->max_w = max;
/*
 * The value might be out of bounds!
 */
	if (W_SCROLLBAR->value > max) {
		redo = True;
		if (W_SCROLLBAR->callback)
			size = (*W_SCROLLBAR->callback)(W_SCROLLBAR->for_widget, max);
		if (size > max)
			size = max;
		if (size < 0)
			size = 0;
		W_SCROLLBAR->value = max;
	}
/*
 * If anything has changed, a redraw is needed
 */
	if (redo)
		lift_resize(W_SCROLLBAR, -1);
}


/**
 ** X11_SCROLLBAR_SET_ARROW
 **	Adjust the arrow for the scrollbar
 **/
void x11__scrollbar_set_arrow(X11_widget wdg, int arrow, int modulo)
{
	W_SCROLLBAR->arrow  = arrow;
	W_SCROLLBAR->modulo = modulo;
}


/**
 ** X11_SCROLLBAR_CALL
 **	Sets the function to call when scrollbar moves
 **/
void x11__scrollbar_call(X11_widget wdg, X11_widget track, x11__callong f)
{
	W_SCROLLBAR->callback   = f;
	W_SCROLLBAR->for_widget = track;
}


/**
 ** X11_SCROLLBAR_ACCELERATORS
 **	Adds accelerators to the scrollbar
 **/
void x11__scrollbar_accelerators(x11__scrollbar *wdg)
{
	X11_Accelerator(&wdg->core, XK_Prior, 0, move_page_up, (void *)wdg);
	X11_Accelerator(&wdg->core, XK_Next,  0, move_page_down,(void *)wdg);
	X11_Accelerator(&wdg->core, XK_Home,  0, move_top, (void *)wdg);
	X11_Accelerator(&wdg->core, XK_End,   0, move_bottom, (void *)wdg);
}


/**
 ** X11_SCROLLBAR_VALUE
 **	Just the position of the scrollbar
 **/
long X11_ScrollbarValue(X11_widget wdg)
{
	return W_SCROLLBAR->value;
}


/**
 ** X11_SCROLLBAR_NEW
 **	Create a scrollbar window
 **/
X11_widget X11_ScrollbarNew(X11_widget parent, char *name, boolean horizontal)
{
x11__scrollbar	*object;
int		m;
/*
 * Get widget
 */
	object = allocator(x11__scrollbar);
	x11__core_setup(&object->core, parent, "Scrollbar", name);
/*
 * Initialize fields
 */
	object->core.event_mask        = ButtonPressMask|ButtonReleaseMask|
					 ExposureMask;
	object->core.on_button_press   = scrollbar_press;
	object->core.on_expose         = scrollbar_expose;
	object->core.on_geometry_check = scrollbar_geometry;
	object->core.on_resize_window  = scrollbar_resize;
/*
 * Resources
 */
	object->core.shadow_thickness  = 2;
	object->core.focus_thickness   = 1;
	object->core.is_active         = True;
	x11__resource_basic(&object->core);
/*
 * Useful value
 */
	if (horizontal)
		object->core.fixed_h = True;
	else
		object->core.fixed_w = True;
	m = 2 * (object->core.focus_thickness + object->core.shadow_thickness);
/*
 * Create the arrows
 */
	object->minus = X11_Arrow(OBJECT_P, horizontal ? X11_ARROW_LEFT
							: X11_ARROW_UP,
						move_minus_one, (void*)object);
	object->plus = X11_Arrow(OBJECT_P, horizontal ? X11_ARROW_RIGHT
							: X11_ARROW_DOWN,
						move_plus_one, (void*)object);
/*
 * We always have a lift
 */
	object->lift = lift_create(OBJECT_P);
/*
 * Ok
 */
	return OBJECT_P;
}
