#include <X11/Intrinsic.h>
#include "VFoo.h"

/* VFooP.h */

typedef struct {
    XtCallbackKind realize_callback_kind;
} VFooClassPart;

typedef struct _VFooClassRec {
    CoreClassPart	core_class;
    CompositeClassPart	composite_class;
    VFooClassPart	v_foo_class;
} VFooClassRec;

extern VFooClassRec vFooClassRec;

typedef struct {
    int v_space, h_space;
    char internal_border_color[6];
    int internal_border;
    int (*realize_proc)();
    XtCallbackList realize_callback;
} VFooPart;

typedef struct _VFooRec {
    CorePart		core;
    CompositePart	composite;
    VFooPart		v_foo;
} VFooRec;

/* VFoo.c */

#include <X11/Atoms.h>
#include <X11/Misc.h>

static XtResource resources[] = {
    { XtNvSpace, XtCVSpace, XrmRInt, sizeof(int),
      XtOffset(VFooWidget, v_foo.v_space), XtRString, "40"},
    { XtNhSpace, XtCHSpace, XrmRInt, sizeof(int),
      XtOffset(VFooWidget, v_foo.h_space), XtRString, "10"},
    { XtNinternalBorder, XtCInternalBorder, XrmRInt, sizeof(int),
      XtOffset(VFooWidget, v_foo.internal_border), XtRString, "1"},
    { XtNinstanceRealizeProc, XtCInstanceRealizeProc, XtRFunction,
      sizeof(XtCallbackProc), XtOffset(VFooWidget, v_foo.realize_proc),
      XtRFunction, (char *)NULL }
};

static void Initialize();
static void Realize();
static void Resize();
static Boolean SetValues();
static XtGeometryResult GeometryManager();
static void ChangeManaged();
static void ClassInitialize();

VFooClassRec vFooClassRec = {
    {
	/* core_class */
	(WidgetClass) &compositeClassRec, /* superclass */
	"VFoo",			/* class_name */
	sizeof(VFooRec),	/* widget_size */
	ClassInitialize,	/* class_initialize */
	FALSE,			/* class_inited */
	Initialize,		/* initialize */
	Realize,		/* realize */
	NULL,			/* actions */
	0,			/* num_actions */
	resources,		/* resources */
	XtNumber(resources),	/* num_resources */
	NULLQUARK,		/* xrm_class */
	TRUE,			/* compress_motion */
	TRUE,			/* compress_exposure */
	FALSE,			/* visible_interest */
	NULL,			/* destroy */
	Resize,			/* resize */
	NULL,			/* expose */
	SetValues,		/* set_values */
	NULL			/* accept_focus */
    }, {
	/* composite_class */
	GeometryManager,	/* geometry_manager */
	ChangeManaged,		/* change_managed */
	NULL,			/* insert_child */
	NULL,			/* delete_child */
	NULL,			/* move_focus_to_next */
	NULL			/* move_focus_to_prev */
    }, {
	/* v_foo_class */
	0			/* realize_callback_kind */
    }
};

WidgetClass vFooWidgetClass = (WidgetClass) &vFooClassRec;

static void ClassInitialize()
{
    CompositeWidgetClass superclass;
    VFooWidgetClass myclass;

    myclass = (VFooWidgetClass) vFooWidgetClass;
    superclass = (CompositeWidgetClass) myclass->core_class.superclass;

    myclass->composite_class.insert_child =
	superclass->composite_class.insert_child;
    myclass->composite_class.delete_child =
	superclass->composite_class.delete_child;
    myclass->v_foo_class.realize_callback_kind =
	XtNewCallbackKind(vFooWidgetClass,
			  XtOffset(VFooWidget, v_foo.realize_callback));
}

/*
 *
 * Do a layout, either actually assigning positions, or just calculating size.
 * Returns TRUE on success; FALSE if it couldn't make things fit.
 *
 */

/* ARGSUSED */
static DoLayout(vfw, width, height, replyWidth, replyHeight, position)
    VFooWidget	vfw;
    Dimension		width, height;
    Dimension		*replyWidth, *replyHeight;	/* RETURN */
    Boolean		position;	/* actually reposition the windows? */
{
    Cardinal  i;
    Dimension w, h;	/* Width and height needed for vfoo	 	*/
    Dimension lw, lh;	/* Width and height needed for current subwindow */
    Dimension h_space;  /* Local copy of vfw->VFoo.h_space 		*/
    Widget    widget;	/* Current subwidget 				*/

    /* VFoo width and height */
    h_space = vfw->v_foo.h_space;
    w = h_space;
    h = vfw->v_foo.v_space;
   
    /* Line width and height */
    lh = 0;
    lw = h_space;
  
    for (i = 0; i < vfw->composite.num_children; i++) {
	widget = vfw->composite.children[i];
	if (widget->core.managed) {
	    if (position) {
		if (lw != widget->core.x || h != widget->core.y)
		    XtMoveWidget(vfw->composite.children[i], (int)lw, (int)h);
		if (vfw->core.width != width)
		    XtResizeWidget(widget, width, widget->core.height,
				   widget->core.border_width);
	    }
	    lh = widget->core.height + 2*widget->core.border_width;
	    h += lh + vfw->v_foo.v_space;
	} /* if managed */
    } /* for */

    *replyWidth = width;
    *replyHeight = h;
}

/*
 *
 * Calculate preferred size, given constraining Foo
 *
 */

static Boolean PreferredSize(vfw, width, height, replyWidth, replyHeight)
    VFooWidget	vfw;
    Dimension		width, height;
    Dimension		*replyWidth, *replyHeight;
{
    DoLayout(vfw, width, height, replyWidth, replyHeight, FALSE);
    return ((*replyWidth <= width) && (*replyHeight <= height));
}

/*
 *
 * Actually layout the V Foo
 *
 */

static void Resize(vfw)
    VFooWidget	vfw;
{
    Dimension junk;

    DoLayout(vfw, vfw->core.width, vfw->core.height, &junk, &junk, TRUE);
} /* Resize */

/*
 *
 * Try to do a new layout within the current width and height;
 * if that fails try to do it within the Foo returned by PreferredSize.
 *
 * TryNewLayout just says if it's possible, and doesn't actually move the kids
 */

static Boolean TryNewLayout(vfw)
    VFooWidget	vfw;
{
    Dimension		width, height;

    if (!PreferredSize(vfw, vfw->core.width, vfw->core.height, &width, &height))
	(void) PreferredSize(vfw, width, height, &width, &height);

    if ((vfw->core.width == width) && (vfw->core.height == height)) {
        /* Same size */
	return (TRUE);
    }

    /* let's see if our parent will go for a new size. */
    switch (XtMakeResizeRequest((Widget) vfw, width, height, &width, &height)) {

	case XtGeometryYes:
	    return (TRUE);

	case XtGeometryNo:
	    return (FALSE);

	case XtGeometryAlmost:
	    if (! PreferredSize(vfw, width, height, &width, &height))
	        return (FALSE);
	    (void) XtMakeResizeRequest((Widget) vfw, width, height, 
					&width, &height);
	    return (TRUE);
    }
}

/*
 *
 * Geometry Manager
 *
 */

/*ARGSUSED*/
static XtGeometryResult GeometryManager(w, request, reply)
    Widget		w;
    XtWidgetGeometry	*request;
    XtWidgetGeometry	*reply;	/* RETURN */

{
    Dimension	width, height, borderWidth, junk;
    VFooWidget vfw;

    /* Position request always denied, as is width change */
    if (request->request_mode & (CWX | CWY | CWWidth))
        return (XtGeometryNo);

    /* Size changes must see if the new size can be accomodated */
    if (request->request_mode & (CWHeight | CWBorderWidth)) {

	/* Make all two fields in the request valid */
	if ((request->request_mode & CWHeight) == 0)
	    request->height = w->core.height;
        if ((request->request_mode & CWBorderWidth) == 0)
	    request->border_width = w->core.border_width;

	/* Save current size and set to new size */
	width = w->core.width;
	height = w->core.height;
	borderWidth = w->core.border_width;
	w->core.width = request->width;
	w->core.height = request->height;
	w->core.border_width = request->border_width;

	/* Decide if new layout works: (1) new V is smaller,
	   (2) new V fits in existing VFoo, (3) VFoo can be
	   expanded to allow new V to fit */

	vfw = (VFooWidget) w->core.parent;
	if (((request->width + request->border_width <= width + borderWidth) &&
	    (request->height + request->border_width <= height + borderWidth))
	|| PreferredSize(vfw, vfw->core.width, vfw->core.height, &junk, &junk)
	|| TryNewLayout(vfw)) {
	    /* Fits in existing or new space, relayout */
	    Resize(vfw);
	    return (XtGeometryYes);
	} else {
	    /* Cannot satisfy request, change back to original geometry */
	    w->core.width = width;
	    w->core.height = height;
	    w->core.border_width = borderWidth;
	    return (XtGeometryNo);
	}
    }; /* if any size changes requested */

    /* Any stacking changes don't make a difference, so allow if that's all */
    return (XtGeometryYes);
}

static void ChangeManaged(vfw)
    VFooWidget vfw;
{
    /* Reconfigure the V Foo */
    (void) TryNewLayout(vfw);
    Resize(vfw);
}

static void Initialize(request, new, args, num_args)
    VFooWidget request, new;
    ArgList args;
    Cardinal num_args;
{
/* ||| What are consequences of letting height, width be 0? If okay, then
       Initialize can be NULL */
    int i;
    if (new->core.width == 0)
        new->core.width =
	    ((new->v_foo.h_space != 0) ? new->v_foo.h_space : 10);
    if (new->core.height == 0)
	new->core.height = 
	    ((new->v_foo.v_space != 0) ? new->v_foo.v_space : 10);
    for (i = 0; i < num_args; i++)
	if (!strcmp(args[i].name, XtNinstanceRealizeProc))
	    new->v_foo.realize_proc = (int (*)())(args[i].value);
/*	    XtAddCallback(new, */
} /* Initialize */

/* ||| Should Realize just return a modified mask and attributes?  Or will some
   of the other parameters change from class to class? */
static void Realize(w, valueMask, attributes)
    register VFooWidget w;
    Mask valueMask;
    XSetWindowAttributes *attributes;
{
    attributes->bit_gravity = NorthWestGravity;
    valueMask |= CWBitGravity;
    
    w->core.window =
	XCreateWindow(XtDisplay(w), XtWindow(w->core.parent),
	w->core.x, w->core.y, w->core.width, w->core.height,
	w->core.border_width, w->core.depth,
	InputOutput, (Visual *)CopyFromParent,
	valueMask, attributes);
    if (w->v_foo.realize_proc)
	(w->v_foo.realize_proc)(w);
} /* Realize */

/*
 *
 * Set Values
 *
 */

static Boolean SetValues (current, request, new, last)
    VFooWidget current, request, new;
    Boolean last;
{
    /* ||| Old code completely bogus, need background, etc., then
    XtMakeGeometryRequest, then relayout */
    return (FALSE);
}
