/* 
 * sxScrollbar.c --
 *
 *	This module implements scrollbars on top of the X window
 *	package.
 *
 * Copyright (C) 1986, 1988 Regents of the University of California.
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /sprite/src/lib/sx/RCS/sxScrollbar.c,v 1.7 89/05/12 13:16:24 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "sx.h"

/*
 * Library imports:
 */

extern char *malloc();

/*
 * The structure type below is used to hold information about a scrollbar:
 */

typedef struct Scrollbar {
    Display *display;		/* Connection to X server. */
    Window window;		/* Window corresponding to scrollbar. */
    unsigned long foreground, background, elevatorColor;
				/* Colors to use when displaying scrollbar. */
    GC gc;			/* Graphics context used to draw stuff in
				 * the scrollbar. */
    int width, height;		/* Dimensions of scrollbar window. */
    float top, bottom;		/* Range of view.  Each number is a fraction
				 * between 0 and 1.  Top gives the position
				 * of the top (or left) edge of the view,
				 * as a fraction of the height (or width)
				 * of the object being displayed.  Bottom
				 * gives the same information for the bottom
				 * (or right) edge of the view. */
    int flags;			/* Miscellaneous flag values:  see below. */
    void (*proc)();		/* Procedure to call when scrollbar is
				 * buttoned.
				 */
    ClientData clientData;	/* Info to pass to proc. */
} Scrollbar;

/*
 * Scrollbar flags:
 *
 * VERTICAL:		1 means scrollbar has vertical orientation, 0
 *			means it has horizontal orientation.
 */

#define VERTICAL 1

/*
 * Scrollbar-related constants:
 *
 * WIDTH -		Default inside width of scrollbars, in pixels.
 * ELEVATOR_MARGIN -	Number of pixels between side of elevator and edge
 *			of scrollbar.
 * END_DISTANCE -	A click within this many pixels of the end of the
 *			scrollbar is assumed to be a click at the end.
 * MIN_ELEVATOR_SIZE -	Elevators are always at least this big, so they
 *			don't get so small they're invisible.
 */

#define WIDTH 11
#define ELEVATOR_MARGIN 3
#define END_DISTANCE 8
#define MIN_ELEVATOR_SIZE 5

/*
 * The following include file defines the stipple pattern used for
 * scrollbar backgrounds:
 */

#include "bitmaps/scrollPattern"
Pixmap scrollPattern;

/*
 * The cursors used by the scrollbar package are defined in separate files
 * so they can be edited with the "bitmap" program.  See the procedure
 * ScrollButtonProc for usage.
 */

#include "bitmaps/left"
#include "bitmaps/leftMask"
#include "bitmaps/right"
#include "bitmaps/rightMask"
#include "bitmaps/up"
#include "bitmaps/upMask"
#include "bitmaps/down"
#include "bitmaps/downMask"
#include "bitmaps/vert"
#include "bitmaps/vertMask"
#include "bitmaps/horiz"
#include "bitmaps/horizMask"
#include "bitmaps/thumbH"
#include "bitmaps/thumbHMask"
#include "bitmaps/thumbV"
#include "bitmaps/thumbVMask"

static Cursor cursorUp, cursorDown, cursorLeft, cursorRight;
static Cursor cursorHoriz, cursorVert, cursorThumbV, cursorThumbH;

/*
 * The context below is used to map from X window ids to Scrollbar structures.
 */

static XContext scrollbarContext;
static int init = 0;

/*
 * Forward references:
 */

static void	ScrollbarInit();
static void	ScrollButtonProc();
static void	ScrollComputeLoc();
static void	ScrollEventProc();
static void	ScrollRedisplay();

/*
 *----------------------------------------------------------------------
 *
 * Sx_ScrollbarMake --
 *
 *	Given an existing window, this procedure sets things up so that
 *	it will behave like a scrollbar.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Data structures are set up so that window will behave like a
 *	scrollbar:  whenever a user clicks a button in the scrollbar,
 *	proc will be invoked in the following way:
 *
 *	void
 *	proc(clientData, distance, units, window)
 *	    ClientData clientData;
 *	    float distance;
 *	    int units;
 *	    Window window;
 *	{
 *	}
 *
 *	The clientData parameter is the same as the parameter by the
 *	same name passed to this routine.  It has no meaning to the
 *	scrollbar code, but is typically used to point to information
 *	relevant to the client.  Distance and units indicate the scroll
 *	action requested by the user, and window of the window parameter
 *	passed to this procedure.  If units is SX_SCROLL_ABSOLUTE,
 *	then distance gives a value between 0 and 1 indicating the
 *	desired positioning of the window's view:  0 means at the very
 *	top or right or beginning and 1 means at the very bottom or
 *	left or end.  Otherwise, units is SX_SCROLL_PAGES, and distance
 *	gives a value between -1 and 1 indicating how much the window
 *	should be scrolled up or down:  1 means the window should be
 *	scrolled up (left)  by its size and -1 means the window should
 *	be scrolled down (right) by its size.
 *
 *	The scrollbar module does not actually adjust the position of
 *	the scrollbar elevator:  proc must do that (if it wishes) by
 *	calling Sx_ScrollbarSetRange.  If proc doesn't call
 *	Sx_ScrollbarSetRange, it means that it wishes to override the
 *	user's request for a change in position and not change anything.
 *
 *----------------------------------------------------------------------
 */

void
Sx_ScrollbarMake(display, window, vertical, foreground, background,
	elevatorColor, proc, clientData)
    Display *display;		/* Connection to X server. */
    Window window;		/* Window to make into scrollbar.  If this
				 * window is already a scrollbar, then the
				 * call just changes the scrollbar's
				 * parameters. */
    int vertical;		/* 1 means use vertical scrollbar
				 * orientation, 0 means horizontal. */
    unsigned long foreground;	/* Pixel value to use for the dark part of
				 * the scrollbar background.  BlackPixel
				 * is typical. */
    unsigned long background;	/* Pixel value to use for the light part of
				 * the scrollbar background.  WhitePixel
				 * is typical. */
    unsigned long elevatorColor;/* Pixel value to use for the elevator that
				 * shows current scroll position.  WhitePixel
				 * is typical. */
    void (*proc)();		/* Procedure to call when scrollbar is buttoned
				 * by user. */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    register Scrollbar *sbPtr;
    caddr_t data;
    XSetWindowAttributes atts;
    XGCValues values;

    if (!init) {
	ScrollbarInit(display);
    }

    /*
     * See if this window is already a scrollbar.  If not, create a new
     * structure for it.
     */

    if (XFindContext(display, window, scrollbarContext, &data) == 0) {
	sbPtr = (Scrollbar *) data;
	XFreeGC(display, sbPtr->gc);
    } else {
	Drawable dum1;
	int dum2;

	sbPtr = (Scrollbar *) malloc(sizeof(Scrollbar));
	sbPtr->display = display;
	sbPtr->window = window;
	XGetGeometry(display, window, &dum1, &dum2, &dum2,
		&sbPtr->width, &sbPtr->height, &dum2, &dum2);
	sbPtr->top = 0.0;
	sbPtr->bottom = 1.0;
	(void) Sx_HandlerCreate(display, window,
		ExposureMask|StructureNotifyMask, ScrollEventProc,
		(ClientData) sbPtr);
	(void) Sx_HandlerCreate(display, window,
		ButtonPressMask|ButtonReleaseMask, ScrollButtonProc,
		(ClientData) sbPtr);
	atts.background_pixmap = None;
	XChangeWindowAttributes(display, window, CWBackPixel, &atts);
	XSaveContext(display, window, scrollbarContext, (caddr_t) sbPtr);
    }

    /*
     * Complete the initialization.
     */

    sbPtr->foreground = foreground;
    sbPtr->background = background;
    sbPtr->elevatorColor = elevatorColor;
    values.stipple = scrollPattern;
    values.background = background;
    sbPtr->gc = XCreateGC(display, window, GCStipple|GCBackground, &values);
    if (vertical) {
	sbPtr->flags = VERTICAL;
	XDefineCursor(display, window, cursorVert);
    } else {
	sbPtr->flags = 0;
	XDefineCursor(display, window, cursorHoriz);
    }
    sbPtr->proc = proc;
    sbPtr->clientData = clientData;

    ScrollRedisplay(sbPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_ScrollbarCreate --
 *
 *	Create a new scrollbar window.  Similar to Sx_ScrollbarMake
 *	except that it creates the window and arranges for the packer
 *	to manage its geometry.
 *
 * Results:
 *	The return value is an X window id for a new scrollbar, which
 *	will always appear on the side'th side of parent.
 *
 * Side effects:
 *	Data structures are set up so that window will behave like a
 *	scrollbar:  whenever a user clicks a button in the scrollbar,
 *	proc will be invoked in the same way as for Sx_MakeScrollbar.
 *
 *----------------------------------------------------------------------
 */

Window
Sx_ScrollbarCreate(display, parent, side, borderSize, foreground, background,
	elevatorColor, borderColor, proc, clientData)
    Display *display;		/* Connection to X server. */
    Window parent;		/* Parent window in which to create new
				 * scrollbar. */
    Sx_Side side;		/* Which side of parent scrollbar should
				 * be displayed on. */
    int borderSize;		/* Width of border for scrollbar. */
    unsigned long foreground;	/* Pixel value to use for the dark part of
				 * the scrollbar background.  BlackPixel
				 * is typical. */
    unsigned long background;	/* Pixel value to use for the light part of
				 * the scrollbar background.  WhitePixel
				 * is typical. */
    unsigned long elevatorColor;/* Pixel value to use for the elevator that
				 * shows current scroll position.  WhitePixel
				 * is typical. */
    unsigned long borderColor;	/* Pixel value to use for the border, if there
				 * is one. */
    void (*proc)();		/* Procedure to call when scrollbar is buttoned
				 * by user. */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    Window w;
    int vertical;

    w = Sx_CreatePacked(display, parent, side, WIDTH, 0, borderSize,
	    borderColor, (Window) 0, background);
    vertical = ((side == SX_LEFT) || (side == SX_RIGHT));
    Sx_ScrollbarMake(display, w, vertical, foreground, background,
	    elevatorColor, proc, clientData);
    return w;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_ScrollbarSetRange --
 *
 *	This procedure is used to indicate the position of the elevator
 *	in a scrollbar.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The elevator will be redisplayed to indicate the fraction
 *	of the object or file that is currently visible in the window.
 *	Top and bottom indicate this fraction.
 *
 *----------------------------------------------------------------------
 */

void
Sx_ScrollbarSetRange(display, window, top, bottom)
    Display *display;		/* Connection to X server. */
    Window window;		/* X id for window containing scrollbar. */
    float top;			/* The position of the top (or left) of the
				 * window's view, relative to the overall
				 * size of the file or object being
				 * displayed.  Must be between 0 and 1.
				 * 0 means the top or left of the window is
				 * at the beginning or top or left of the
				 * file or object, and 1 means it's at the
				 * bottom or right of the object. */
    float bottom;		/* Similar to top, but indicates where the
				 * bottom or right side of the view is
				 * located. */
{
    register Scrollbar *sbPtr;
    caddr_t data;

    if (!init) {
	ScrollbarInit(display);
    }
    
    /*
     * Locate our data structures for the scrollbar.
     */

    if (XFindContext(display, window, scrollbarContext, &data) != 0) {
	Sx_Panic(display, "Sx_ScrollbarSetRange:  not a scrollbar window.");
    }
    sbPtr = (Scrollbar *) data;

    if (top > 1.0) {
	top = 1.0;
    } else if (top < 0.0) {
	top = 0.0;
    }
    if (bottom > 1.0) {
	bottom = 1.0;
    } else if (bottom < top) {
	bottom = top;
    }
    if ((sbPtr->top == top) && (sbPtr->bottom == bottom)) {
	return;
    }
    sbPtr->top = top;
    sbPtr->bottom = bottom;
    ScrollRedisplay(sbPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_ScrollbarGetRange --
 *
 *	Returns the current range displayed in the scrollbar.
 *
 * Results:
 *	The values pointed to by topPtr and bottomPtr are filled
 *	in with the last values of top and bottom passed to
 *	Sx_ScrollbarSetRange for this scrollbar.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
Sx_ScrollbarGetRange(display, window, topPtr, bottomPtr)
    Display *display;		/* Connection to X server. */
    Window window;		/* X id for window containing scrollbar. */
    float *topPtr;		/* Put top value (between 0 and 1) here. */
    float *bottomPtr;		/* Put bottom value (between 0 and 1) here. */
{
    register Scrollbar *sbPtr;
    caddr_t data;

    if (!init) {
	ScrollbarInit(display);
    }
    
    /*
     * Locate our data structure for the scrollbar.
     */

    if (XFindContext(display, window, scrollbarContext, &data) != 0) {
	Sx_Panic(display, "Sx_ScrollbarGetRange:  not a scrollbar window.");
    }
    sbPtr = (Scrollbar *) data;

    *topPtr = sbPtr->top;
    *bottomPtr = sbPtr->bottom;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_ScrollbarWidth --
 *
 *	Returns a suggested width for scrollbars, in pixels.
 *
 * Results:
 *	The return value is the suggested width of scrollbars, in pixels
 *	not including the border.  This may be of use to clients in
 *	laying out windows.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
Sx_ScrollbarWidth()
{
    return WIDTH;
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollbarInit --
 *
 *	Performs once-only initialization for the scrollbar module.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Generally-useful stuff, like the cursors, gets created and set up.
 *
 *----------------------------------------------------------------------
 */

static void
ScrollbarInit(display)
    Display *display;		/* Connection to X server. */
{
    static XColor black = {0, 0, 0, 0, 0};
    static XColor white = {0, ~0, ~0, ~0, 0};
    Pixmap source, mask;
    Window root;

    scrollbarContext = XUniqueContext();
    root = RootWindow(display, DefaultScreen(display));
    scrollPattern = XCreateBitmapFromData(display, root,
	    scrollPattern_bits, scrollPattern_width, scrollPattern_height);

    source = XCreateBitmapFromData(display, root, up_bits,
	    up_width, up_height);
    mask = XCreateBitmapFromData(display, root, upMask_bits,
	    upMask_width, upMask_height);
    cursorUp = XCreatePixmapCursor(display, source, mask, &black, &white,
	    up_x_hot, up_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, down_bits,
	    down_width, down_height);
    mask = XCreateBitmapFromData(display, root, downMask_bits,
	    downMask_width, downMask_height);
    cursorDown = XCreatePixmapCursor(display, source, mask, &black, &white,
	    down_x_hot, down_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, left_bits,
	    left_width, left_height);
    mask = XCreateBitmapFromData(display, root, leftMask_bits,
	    leftMask_width, leftMask_height);
    cursorLeft = XCreatePixmapCursor(display, source, mask, &black, &white,
	    left_x_hot, left_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, right_bits,
	    right_width, right_height);
    mask = XCreateBitmapFromData(display, root, rightMask_bits,
	    rightMask_width, rightMask_height);
    cursorRight = XCreatePixmapCursor(display, source, mask, &black, &white,
	    right_x_hot, right_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, vert_bits,
	    vert_width, vert_height);
    mask = XCreateBitmapFromData(display, root, vertMask_bits,
	    vertMask_width, vertMask_height);
    cursorVert = XCreatePixmapCursor(display, source, mask, &black, &white,
	    vert_x_hot, vert_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, horiz_bits,
	    horiz_width, horiz_height);
    mask = XCreateBitmapFromData(display, root, horizMask_bits,
	    horizMask_width, horizMask_height);
    cursorHoriz = XCreatePixmapCursor(display, source, mask, &black, &white,
	    horiz_x_hot, horiz_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, thumbV_bits,
	    thumbV_width, thumbV_height);
    mask = XCreateBitmapFromData(display, root, thumbVMask_bits,
	    thumbVMask_width, thumbVMask_height);
    cursorThumbV = XCreatePixmapCursor(display, source, mask, &black, &white,
	    thumbV_x_hot, thumbV_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    source = XCreateBitmapFromData(display, root, thumbH_bits,
	    thumbH_width, thumbH_height);
    mask = XCreateBitmapFromData(display, root, thumbHMask_bits,
	    thumbHMask_width, thumbHMask_height);
    cursorThumbH = XCreatePixmapCursor(display, source, mask, &black, &white,
	    thumbH_x_hot, thumbH_y_hot);
    XFreePixmap(display, source);
    XFreePixmap(display, mask);

    init = 1;
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollRedisplay --
 *
 *	This procedure is invoked to redisplay scrollbars.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The scrollbar gets redisplayed.
 *
 *----------------------------------------------------------------------
 */

static void
ScrollRedisplay(sbPtr)
    register Scrollbar *sbPtr;		/* Scrollbar to be redisplayed. */
{
    int x, y, width, height;

    /*
     * Fill the window with the background stipple pattern.
     */

    XSetForeground(sbPtr->display, sbPtr->gc, sbPtr->foreground);
    XSetFillStyle(sbPtr->display, sbPtr->gc, FillOpaqueStippled);
    XFillRectangle(sbPtr->display, sbPtr->window, sbPtr->gc, 0, 0,
	    sbPtr->width, sbPtr->height);
    XSetFillStyle(sbPtr->display, sbPtr->gc, FillSolid);

    /*
     * Compute location of elevator bar, then display it.
     */

    if (sbPtr->flags & VERTICAL) {
	x = ELEVATOR_MARGIN;
	y = sbPtr->height*sbPtr->top;
	width = sbPtr->width - 2*ELEVATOR_MARGIN;
	height = sbPtr->height*sbPtr->bottom;
	height -= y;
	if (height < MIN_ELEVATOR_SIZE) {
	    y -= (MIN_ELEVATOR_SIZE-height)/2;
	    height = MIN_ELEVATOR_SIZE;
	}
	if (y < 0) {
	    y = 0;
	}
	if ((y+height) > sbPtr->height) {
	    y = sbPtr->height - height;
	}
    } else {
	x = sbPtr->width*sbPtr->top;
	y = ELEVATOR_MARGIN;
	width = sbPtr->width*sbPtr->bottom;
	width -= x;
	height = sbPtr->height - 2*ELEVATOR_MARGIN;
	if (width < MIN_ELEVATOR_SIZE) {
	    x -= (MIN_ELEVATOR_SIZE-height)/2;
	    width = MIN_ELEVATOR_SIZE;
	}
	if (x < 0) {
	    x = 0;
	}
	if ((x+width) > sbPtr->width) {
	    x = sbPtr->width - width;
	}
    }

    XSetForeground(sbPtr->display, sbPtr->gc, sbPtr->elevatorColor);
    XFillRectangle(sbPtr->display, sbPtr->window, sbPtr->gc, x, y,
	    width, height);
    XSetForeground(sbPtr->display, sbPtr->gc, sbPtr->foreground);
    XDrawRectangle(sbPtr->display, sbPtr->window, sbPtr->gc, x, y,
	    width-1, height-1);
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollEventProc --
 *
 *	Invoked by the Sx_ dispatcher for all events on scrollbar
 *	windows except button events.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the event:  the window may get redisplayed, or
 *	our internal structures may get recycled (on window delete).
 *
 *----------------------------------------------------------------------
 */

static void
ScrollEventProc(sbPtr, eventPtr)
    Scrollbar *sbPtr;		/* Scrollbar that was exposed. */
    XEvent *eventPtr;		/* Gives information about event. */
{
    switch (eventPtr->type) {

	case Expose:
	    /*
	     * When expose events come in bunches, ignore all but the
	     * last in the bunch (the whole window gets redisplayed
	     * anyway).
	     */
	    
	    if (eventPtr->xexpose.count == 0) {
		ScrollRedisplay(sbPtr);
	    }
	    return;

	case ConfigureNotify:
	    sbPtr->width = eventPtr->xconfigure.width;
	    sbPtr->height = eventPtr->xconfigure.height;
	    return;

	case DestroyNotify:
	    XDeleteContext(sbPtr->display, sbPtr->window, scrollbarContext);
	    XFreeGC(sbPtr->display, sbPtr->gc);
	    free((char *) sbPtr);
	    return;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ScrollButtonProc --
 *
 *	This procedure is invoked by the Sx_ dispatcher whenever
 *	a button is pressed or released inside a scrollbar window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Calls client-supplied procedure if scrolling has been
 *	requested.
 *
 *----------------------------------------------------------------------
 */

static void
ScrollButtonProc(sbPtr, eventPtr)
    register Scrollbar *sbPtr;		/* Scrollbar being buttoned. */
    register XButtonEvent *eventPtr;	/* Describes what happened. */
{
    float distance;
    int units;

    if ((eventPtr->subwindow != NULL)
	    && (eventPtr->subwindow != sbPtr->window)) {
	return;
    }

    /*
     * When a button goes down, just change the cursor to reflect the
     * operation that's in progress.
     */

    if (eventPtr->type == ButtonPress) {
	if (sbPtr->flags & VERTICAL) {
	    if (eventPtr->button == Button1) {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorUp);
	    } else if (eventPtr->button == Button3) {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorDown);
	    } else {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorThumbV);
	    }
	} else {
	    if (eventPtr->button == Button1) {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorLeft);
	    } else if (eventPtr->button == Button3) {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorRight);
	    } else {
		XDefineCursor(sbPtr->display, sbPtr->window, cursorThumbH);
	    }
	}
	return;
    }

    /*
     * Button release.  Restore the cursor, and process the command.
     */

    if (sbPtr->flags & VERTICAL) {
	XDefineCursor(sbPtr->display, sbPtr->window, cursorVert);
	distance = eventPtr->y;
	if (distance < END_DISTANCE) {
	    distance = 0;
	}
	if (distance > (sbPtr->height - END_DISTANCE)) {
	    distance = sbPtr->height;
	}
	distance /= sbPtr->height;
    } else {
	XDefineCursor(sbPtr->display, sbPtr->window, cursorHoriz);
	distance = eventPtr->x;
	if (distance < END_DISTANCE) {
	    distance = 0;
	}
	if (distance > (sbPtr->width - END_DISTANCE)) {
	    distance = sbPtr->width;
	}
	distance /= sbPtr->width;
    }

    if (eventPtr->button == Button1) {
	units = SX_SCROLL_PAGES;
    } else if (eventPtr->button == Button3) {
	units = SX_SCROLL_PAGES;
	distance = -distance;
    } else {
	units = SX_SCROLL_ABSOLUTE;
    }

    (*sbPtr->proc)(sbPtr->clientData, distance, units, sbPtr->window);
    
    /*
     * It's important that we do NOTHING to the scrollbar after calling
     * the client, since the client could conceivable have deleted the
     * scrollbar, leaving us with a dangling pointer.
     */
}
