/* 
 * sxPack.c --
 *
 *	This file provides simple layout management within a window
 *	by packing subwindows from the outside in to fill the available
 *	space.
 *
 * 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 and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation.  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/sxPack.c,v 1.4 89/05/11 11:11:33 ouster Exp $ SPRITE (Berkeley)";
#endif not lint

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

/*
 * Library imports:
 */

extern char *malloc();

/*
 * Records of the type below are used to keep track of windows for
 * packing purposes.  Each window that's involved in packing, either
 * because its location and size are managed automatically or because
 * it has other windows packed inside it (or both), has an associated
 * record.
 */

typedef struct Packer{
    List_Links links;		/* Refers to siblings that are also packed
				 * inside this window's parent.  Being earlier
				 * on this list gives priority in space
				 * allocation. */
    Display *display;		/* Display for these windows. */
    Window w;			/* X's identifier for this window. */
    int x, y;			/* Location of window inside parent. */
    int width, height;		/* Last known dimensions of window (inside
				 * dimensions, not including border). */
    struct Packer *parentPtr;	/* Info describing parent.  NULL means this
				 * window isn't packed right now. */
    Sx_Side side;		/* Side of cavity that window abuts. */
    int size;			/* Mimimum size of window in pixels, not
				 * including border,in direction perpendicular
				 * to side. */
    int borderSize;		/* The width of border, in direction
				 * perpendicular to side.   If there's no
				 * border then this is zero. */
    Window border;		/* X's identifier for the special window
				 * created as a border separating w from
				 * the next window in packing order.  If
				 * there's no border, this is 0. */
    List_Links children;	/* Head of list of children packed inside
				 * this window. */
    int updateCount;		/* If non-zero, then don't actually update
				 * any window dimensions until it becomes
				 * zero. */
    int flags;			/* Miscellaneous flag values.  See below. */
} Packer;

/*
 * Flag values for Packers:
 *
 * EXPANDABLE:		0 means size must be exact.  1 means size is
 *			a minimum:  let window expand to fill unclaimed
 *			space.  All expandable left or right windows share
 *			unclaimed x space, and all expandable top or bottom
 *			windows share unclaimed y space.
 */

#define EXPANDABLE	1

/*
 * Context information used to keep track of all the Packers.
 */

static XContext packingContext;
static int initialized = 0;

/*
 * Forward references to procedures defined later in this file:
 */

static void		ArrangePacking();
static void		PackStructureProc();

/*
 *----------------------------------------------------------------------
 *
 * Sx_Pack --
 *
 *	Pack a child window inside a parent window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	From  now on, child's size and location will be managed
 *	automatically.  Child will be placed along one side of
 *	the cavity remaining inside parent after packing all
 *	children from prior calls to Sx_Pack.  If size is greater
 *	than zero, it specifies the size of child in the direction
 *	perpendicular to side.  The child will completely fill the
 *	cavity along the given side, and will fill the entire
 *	remaining cavity if size is <= 0.  Once this child is placed,
 *	other children (specified in later calls to Sx_Pack)
 *	will only use the space that remains inside parent's cavity
 *	after this child (which may be none).  If before or after
 *	is given, the child will be inserted in the packing order,
 *	either before before or after after, rather than at the
 *	end of the packing order.
 *
 *----------------------------------------------------------------------
 */

void
Sx_Pack(display, child, parent, side, size, expandable, borderSize,
	borderPixel, before)
    Display *display;		/* Display for parent and child. */
    Window child;		/* Window whose size and location are to
				 * be managed automatically from now on.
				 * Must not already be managed by packer,
				 * and must not be mapped right now.  This
				 * module takes care of mapping the child,
				 * either now (if the parent has already
				 * been mapped) or when the parent gets
				 * mapped. */
    Window parent;		/* Parent of child;  child will be packed
				 * in here.  Caller must eventually arrange
				 * for this window to be mapped, if it isn't
				 * already mapped. */
    Sx_Side side;		/* Which side of parent child should be
				 * lined up against: SX_TOP, etc.  */
    int size;			/* Minimum dimension of child (not including
				 * border) in direction perpendicular to
				 * side. */
    int expandable;		/* 0 means window must have dimension
				 * exactly equal to size.  1 means expand
				 * child if possible to fill unclaimed
				 * space.  If multiple children are all
				 * expandable, they share the space equally. */
    int borderSize;		/* If non-zero, a border of this size will
				 * be generated on the "side" side of child. */
    unsigned long borderPixel;	/* Color to use for border.  Ignored if
				 * borderSize is 0. */
    Window before;		/* If non-zero, then insert child in order
				 * before this window.  If zero, insert child
				 * at the end of the packing order for
				 * parent. */
{
    register Packer *parentPtr, *childPtr;
    Packer *child2Ptr;
    XSetWindowAttributes atts;
    caddr_t data;

    /*
     * First find the information for the parent, and create a new
     * Packer if we don't already know about the parent.  Before
     * that, build the association table if it didn't exist already.
     */

    if (!initialized) {
	packingContext = XUniqueContext();
	initialized = 1;
    }
    if (XFindContext(display, parent, packingContext, &data) != 0) {
	XWindowAttributes info;

	XGetWindowAttributes(display, parent, &info);
	parentPtr = (Packer *) malloc(sizeof(Packer));
	List_InitElement(&parentPtr->links);
	parentPtr->display = display;
	parentPtr->w = parent;
	parentPtr->x = info.x;
	parentPtr->y = info.y;
	parentPtr->width = info.width;
	parentPtr->height = info.height;
	parentPtr->parentPtr = NULL;
	parentPtr->borderSize = 0;
	parentPtr->border = 0;
	List_Init(&parentPtr->children);
	parentPtr->updateCount = 0;
	parentPtr->flags = 0;
	(void) Sx_HandlerCreate(display, parent, StructureNotifyMask,
		PackStructureProc, (ClientData) parentPtr);
	XSaveContext(display, parent, packingContext, (caddr_t) parentPtr);
    } else {
	parentPtr = (Packer *) data;
    }

    /*
     * See if there's already a Packer for the child (it might be a parent
     * too).  If so, it better not already be a child.  If there isn't
     * already a Packer for the child, make one.
     */

    if (XFindContext(display, child, packingContext, &data) != 0) {
	childPtr = (Packer *) malloc(sizeof(Packer));
	List_InitElement(&childPtr->links);
	childPtr->display = display;
	childPtr->w = child;
	childPtr->width = childPtr->height = 0;
	List_Init(&childPtr->children);
	childPtr->updateCount = 0;
	childPtr->flags = 0;
	(void) Sx_HandlerCreate(display, child, StructureNotifyMask,
		PackStructureProc, (ClientData) childPtr);
	XSaveContext(display, child, packingContext, (caddr_t) childPtr);
    } else {
	childPtr = (Packer *) data;
	if (childPtr->parentPtr != NULL) {
	    Sx_Panic(display, "Sx_Pack: child was already packed.");
	}
    }

    /*
     * Make sure that the parent isn't already somehow a child of
     * the child.  A cycle would cause all sorts of bizarre behavior.
     */
    
    for (child2Ptr = parentPtr; child2Ptr != NULL;
	    child2Ptr = child2Ptr->parentPtr) {
	if (child2Ptr == childPtr) {
	    Sx_Panic(display,
		    "Sx_Pack: parent was already packed as descendant of child.  Can't have circular packings.");
	}
    }
    
    /*
     * Insert the child in the packing order for the parent.  Then
     * reconfigure the windows inside the parent.
     */

    atts.win_gravity = UnmapGravity;
    XChangeWindowAttributes(display, child, CWWinGravity, &atts);
    childPtr->parentPtr = parentPtr;
    childPtr->side = side;
    childPtr->size = size;
    childPtr->borderSize = borderSize;
    if (borderSize > 0) {
	atts.bit_gravity = ForgetGravity;
	atts.background_pixel = borderPixel;
	childPtr->border = XCreateWindow(display, parent, 0, 0, 1, 1,
		0, CopyFromParent, InputOutput, CopyFromParent,
		CWBackPixel|CWBitGravity|CWWinGravity, &atts);
    } else {
	childPtr->border = 0;
    }
    if (expandable) {
	childPtr->flags |= EXPANDABLE;
    } else {
	childPtr->flags &= ~EXPANDABLE;
    }


    if (before != NULL) {
	if ((XFindContext(display, before, packingContext,
		(caddr_t *) &child2Ptr) != 0)
		|| (child2Ptr->parentPtr != parentPtr))  {
	    Sx_Panic(display,
		    "Sx_Pack: tried to pack next to a window that isn't \
packed or isn't a sibling.");
	}
	List_Insert(&childPtr->links, LIST_BEFORE(&child2Ptr->links));
    } else {
	List_Insert(&childPtr->links, LIST_ATREAR(&parentPtr->children));
    }

    ArrangePacking(parentPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_CreatePacked --
 *
 *	Like Sx_Pack, except create the window to be packed.
 *
 * Results:
 *	The return value is a new window whose size and location
 *	will be managed automatically.  If parent is mapped, new child
 *	will be too;  otherwise, child will be mapped automatically
 *	when parent becomes mapped.  See the comments for Sx_Pack
 *	for information on how the layout is managed.
 *
 * Side effects:
 *	A new window is created and packing information is set up.
 *
 *----------------------------------------------------------------------
 */

Window
Sx_CreatePacked(display, parent, side, size, expandable, borderSize,
	borderPixel, before, background)
    Display *display;		/* Display for parent and child. */
    Window parent;		/* Parent of child;  child will be packed
				 * in here.  Caller must eventually arrange
				 * for this window to be mapped, if it isn't
				 * already mapped. */
    Sx_Side side;		/* Which side of parent child should be
				 * lined up against: SX_TOP, etc.  */
    int size;			/* Minimum dimension of child (not including
				 * border) in direction perpendicular to
				 * side. */
    int expandable;		/* 0 means window must have dimension
				 * exactly equal to size.  1 means expand
				 * child if possible to fill unclaimed
				 * space.  If multiple children are all
				 * expandable, they share the space equally. */
    int borderSize;		/* If non-zero, a border of this size will
				 * be generated on the "side" side of child. */
    unsigned long borderPixel;	/* Pixel value to use for border.  Ignored if
				 * borderSize is 0. */
    Window before;		/* If non-zero, then insert child in order
				 * before this window.  If zero, insert child
				 * at the end of the packing order for
				 * parent. */
    unsigned long background;	/* Pixel to use for background. */
{
    Window w;

    w = XCreateSimpleWindow(display, parent, 0, 0, 1, 1, 0, background,
	    background);
    Sx_Pack(display, w, parent, side, size, expandable, borderSize,
	    borderPixel, before);
    return w;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_Unpack --
 *	
 *	Don't manage window's location and size anymore.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Window is removed from the packing list for its parent.  It's
 *	also unmapped.
 *
 *----------------------------------------------------------------------
 */

void
Sx_Unpack(display, window)
    Display *display;		/* Display containing window. */
    Window window;		/* Window whose layout is no longer to be
				 * managed automatically. */
{
    register Packer *childPtr;
    caddr_t data;

    if (packingContext == NULL) {
	return;
    }
    if (XFindContext(display, window, packingContext, &data) != 0) {
	return;
    }
    childPtr = (Packer *) data;
    if (childPtr->parentPtr == NULL) {
	return;
    }

    List_Remove(&childPtr->links);
    XUnmapWindow(childPtr->display, childPtr->w);
    childPtr->width = childPtr->height = 0;
    if (childPtr->border != 0) {
	XDestroyWindow(childPtr->display, childPtr->border);
	childPtr->border = childPtr->borderSize = 0;
    }
    ArrangePacking(childPtr->parentPtr);
    childPtr->parentPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_PackUpdateOff --
 *
 *	From now on, the packer will not actually adjust the sizes of
 *	children of window;  it will just remember information and
 *	delay the resizing until Sx_PackUpdateOn is called.  This is
 *	useful when many children are about to be repacked:  it avoids
 *	multiple redisplays.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Children of window will not be resized anymore.
 *
 *----------------------------------------------------------------------
 */

void
Sx_PackUpdateOff(display, window)
    Display *display;		/* Information about display. */
    Window window;		/* Window for which to delay updates. */
{
    Packer *parentPtr;

    if (!initialized) {
	return;
    }
    if (XFindContext(display, window, packingContext,
	    (caddr_t *) &parentPtr) == 0) {
	parentPtr->updateCount++;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Sx_PackUpdateOn --
 *
 *	Re-enable window size updates.  If all Sx_PackUpdateOff calls
 *	have now been cancelled, perform any updates that were delayed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Children of window will be resized again.
 *
 *----------------------------------------------------------------------
 */

void
Sx_PackUpdateOn(display, window)
    Display *display;		/* Display containing window. */
    Window window;		/* Window for which to delay updates. */
{
    Packer *parentPtr;

    if (!initialized) {
	return;
    }
    if ((XFindContext(display, window, packingContext,
	    (caddr_t *) &parentPtr) == 0)
	    && (parentPtr->updateCount > 0)) {
	parentPtr->updateCount--;
	if (parentPtr->updateCount == 0) {
	    ArrangePacking(parentPtr);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ArrangePacking --
 *
 *	This procedure scans through the packing list for a parent
 *	window, computing the correct size for each child and modifying
 *	that child's size if it isn't already correct.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Windows' sizes get changed.
 *
 *----------------------------------------------------------------------
 */

static void
ArrangePacking(parentPtr)
    register Packer *parentPtr;		/* Pointer to structure describing
					 * containing window that is to be
					 * re-arranged. */
{
    register Packer *childPtr;
    int x, y, width, height, spareWidth, spareHeight;
    int numExpWidth, numExpHeight, residualWidth, residualHeight;
    int cavityX, cavityY, cavityWidth, cavityHeight;
    int borderX, borderY, borderWidth, borderHeight;

    /*
     * Stop right here if updating has been disabled.
     */

    if (parentPtr->updateCount != 0) {
	return;
    }

    /*
     * Make two passes over the window's packing list.  In the first pass,
     * figure out how much spare vertical and horizontal space there is,
     * and how many windows are expandable in that direction.  In the second
     * pass, assign actual window sizes.
     */

    spareWidth = parentPtr->width;
    spareHeight = parentPtr->height;
    numExpWidth = numExpHeight = 0;
    LIST_FORALL(&parentPtr->children, ((List_Links *) childPtr)) {
	if ((childPtr->side == SX_TOP) || (childPtr->side == SX_BOTTOM)) {
	    spareHeight -= childPtr->size + childPtr->borderSize;
	    if (childPtr->flags & EXPANDABLE) {
		numExpHeight++;
	    }
	} else {
	    spareWidth -= childPtr->size + childPtr->borderSize;
	    if (childPtr->flags & EXPANDABLE) {
		numExpWidth++;
	    }
	}
    }
    if ((spareWidth <= 0) || (numExpWidth == 0)) {
	spareWidth = 0;
	residualWidth = 0;
    } else {
	residualWidth = spareWidth % numExpWidth;
	spareWidth = spareWidth / numExpWidth;
    }
    if ((spareHeight <= 0) || (numExpHeight == 0)) {
	spareHeight = 0;
	residualHeight = 0;
    } else {
	residualHeight = spareHeight % numExpHeight;
	spareHeight = spareHeight / numExpHeight;
    }

    cavityX = cavityY = 0;
    cavityWidth = parentPtr->width;
    cavityHeight = parentPtr->height;
    LIST_FORALL(&parentPtr->children, ((List_Links *) childPtr)) {
	if ((childPtr->side == SX_TOP) || (childPtr->side == SX_BOTTOM)) {
	    borderWidth = width = cavityWidth;
	    height = childPtr->size;
	    if (childPtr->flags & EXPANDABLE) {
		height += spareHeight;
		numExpHeight--;
		if (numExpHeight == 0) {
		    height += residualHeight;
		}
	    }
	    borderHeight = childPtr->borderSize;
	    cavityHeight -= height + childPtr->borderSize;
	    if (cavityHeight < 0) {
		height += cavityHeight;
		cavityHeight = 0;
	    }
	    borderX = x = cavityX;
	    if (childPtr->side == SX_TOP) {
		y = cavityY;
		borderY = y + height;
		cavityY = borderY + borderHeight;
	    } else {
		borderY = cavityY + cavityHeight;
		y = borderY + borderHeight;
	    }
	} else {
	    borderHeight = height = cavityHeight;
	    width = childPtr->size;
	    if (childPtr->flags & EXPANDABLE) {
		width += spareWidth;
		numExpWidth--;
		if (numExpWidth == 0) {
		    width += residualWidth;
		}
	    }
	    borderWidth = childPtr->borderSize;
	    cavityWidth -= width + childPtr->borderSize;
	    if (cavityWidth < 0) {
		width += cavityWidth;
		cavityWidth = 0;
	    }
	    borderY = y = cavityY;
	    if (childPtr->side == SX_LEFT) {
		x = cavityX;
		borderX = x + width;
		cavityX = borderX + borderWidth;
	    } else {
		borderX = cavityX + cavityWidth;
		x = borderX + borderWidth;
	    }
	}

	/*
	 * If the child is too small to be interesting, then unmap it.
	 * Otherwise, map it and set its size and shape (if they've
	 * changed.
	 */

	if ((width <= 0) || (height <= 0)) {
	    XUnmapWindow(childPtr->display, childPtr->w);
	    if (childPtr->border != 0) {
		XUnmapWindow(childPtr->display, childPtr->border);
	    }
	} else {
	    if ((x != childPtr->x) || (y != childPtr->y)
		    || (width != childPtr->width)
		    || (height != childPtr->height)) {
		XWindowChanges changes;

		changes.x = x; changes.y = y;
		changes.width = width; changes.height = height;
		changes.stack_mode = Above;
		XConfigureWindow(childPtr->display, childPtr->w,
			CWX|CWY|CWWidth|CWHeight|CWStackMode, &changes);
		if (childPtr->border != 0) {
		    changes.x = borderX; changes.y = borderY;
		    changes.width = borderWidth; changes.height = borderHeight;
		    XConfigureWindow(childPtr->display, childPtr->border,
			    CWX|CWY|CWWidth|CWHeight|CWStackMode, &changes);
		}
		childPtr->x = x;
		childPtr->y = y;
		childPtr->width = width;
		childPtr->height = height;
	    }
	    XMapWindow(childPtr->display, childPtr->w);
	    if (childPtr->border != 0) {
		XMapWindow(childPtr->display, childPtr->border);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PackStructureProc --
 *
 *	Called by the Sx dispatcher whenever a structural change
 *	occurs to a window managed by the packer (both parents
 *	and children).  The two events that this procedure cares
 *	about are window destruction and changes to size.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When size changes occur to a parent, the sizes and locations
 *	of children get re-arranged.  When a window is destroyed,
 *	our data structures get cleaned up.
 *
 *----------------------------------------------------------------------
 */

static void
PackStructureProc(packPtr, eventPtr)
    register Packer *packPtr;		/* Information about window that
					 * event pertains to. */
    register XEvent *eventPtr;		/* Event that occurred for window. */
{
    register Packer *childPtr;

    if (eventPtr->type == ConfigureNotify) {
	packPtr->height = eventPtr->xconfigure.height;
	packPtr->width = eventPtr->xconfigure.width;
	ArrangePacking(packPtr);
    } else if (eventPtr->type == DestroyNotify) {

	/*
	 * Unlink from its parent, if any.
	 */

	if (packPtr->parentPtr != NULL) {
	    List_Remove(&packPtr->links);
	    ArrangePacking(packPtr->parentPtr);
	}

	/*
	 * Warning: can't delete the border window in this case, because
	 * it might already have been deleted (if all the children of the
	 * parent are being deleted).
	 */
    
	/*
	 * Remove children from this parent's list.  We'll get called again
	 * to do the rest of the cleanup on the children.
	 */
    
	LIST_FORALL(&packPtr->children, (List_Links *) childPtr) {
	    List_Remove(&childPtr->links);
	    childPtr->parentPtr = NULL;
	}

	XDeleteContext(packPtr->display, packPtr->w, packingContext);
	free((char *) packPtr);
    }
}
