/*
 * $XConsortium: Porthole.c,v 1.14 91/03/14 16:48:01 converse Exp $
 *
 * Copyright 1990 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:  Jim Fulton, MIT X Consortium
 * 
 * This widget is a trivial clipping widget.  It is typically used with a
 * panner or scrollbar to navigate.
 */

#include <X11/IntrinsicP.h>		/* get basic toolkit stuff */
#include <X11/StringDefs.h>		/* get XtN and XtC defines */
#include <X11/Xaw/XawInit.h>		/* get Xaw initialize stuff */
#include <X11/Xaw/PortholeP.h>		/* get porthole structs */
#include <X11/Xmu/Misc.h>		/* for MAX */


/*
 * resources for the porthole
 */
static XtResource resources[] = {
#define poff(field) XtOffsetOf(PortholeRec, porthole.field)
    { XtNreportCallback, XtCReportCallback, XtRCallback, sizeof(XtPointer),
	poff(report_callbacks), XtRCallback, (XtPointer) NULL },
#undef poff
};


/*
 * widget class methods used below
 */
static void Realize();			/* set gravity and upcall */
static void Resize();			/* report new size */
static XtGeometryResult GeometryManager();  /* deal with child requests */
static void ChangeManaged();		/* somebody added a new widget */
static XtGeometryResult QueryGeometry();  /* say how big would like to be */

PortholeClassRec portholeClassRec = {
  { /* core fields */
    /* superclass		*/	(WidgetClass) &compositeClassRec,
    /* class_name		*/	"Porthole",
    /* widget_size		*/	sizeof(PortholeRec),
    /* class_initialize		*/	XawInitializeWidgetSet,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	NULL,
    /* initialize_hook		*/	NULL,
    /* realize			*/	Realize,
    /* actions			*/	NULL,
    /* num_actions		*/	0,
    /* resources		*/	resources,
    /* num_resources		*/	XtNumber(resources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	NULL,
    /* resize			*/	Resize,
    /* expose			*/	NULL,
    /* set_values		*/	NULL,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	NULL,
    /* query_geometry		*/	QueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },
  { /* composite fields */
    /* geometry_manager		*/	GeometryManager,
    /* change_managed		*/	ChangeManaged,
    /* insert_child		*/	XtInheritInsertChild,
    /* delete_child		*/	XtInheritDeleteChild,
    /* extension		*/	NULL
  },
  { /* porthole fields */
    /* ignore                   */	0
  }
};

WidgetClass portholeWidgetClass = (WidgetClass) &portholeClassRec;


/*****************************************************************************
 *                                                                           *
 *			       utility routines                              *
 *                                                                           *
 *****************************************************************************/

static Widget find_child (pw)
    register PortholeWidget pw;
{
    register Widget *children;
    register int i;

    /*
     * Find the managed child on which we should operate.  Ignore multiple
     * managed children.
     */
    for (i = 0, children = pw->composite.children;
	 i < pw->composite.num_children; i++, children++) {
	if (XtIsManaged(*children)) return *children;
    }

    return (Widget) NULL;
}

static void SendReport (pw, changed)
    PortholeWidget pw;
    unsigned int changed;
{
    Widget child = find_child (pw);

    if (pw->porthole.report_callbacks && child) {
	XawPannerReport prep;

	prep.changed = changed;
	prep.slider_x = -child->core.x;	/* porthole is "inner" */
	prep.slider_y = -child->core.y;	/* child is outer since it is larger */
	prep.slider_width = pw->core.width;
	prep.slider_height = pw->core.height;
	prep.canvas_width = child->core.width;
	prep.canvas_height = child->core.height;
	XtCallCallbackList ((Widget)pw, pw->porthole.report_callbacks,
			    (XtPointer) &prep);
    }
}


static void layout_child (pw, child, geomp, xp, yp, widthp, heightp)
    PortholeWidget pw;
    Widget child;
    XtWidgetGeometry *geomp;
    Position *xp, *yp;
    Dimension *widthp, *heightp;
{
    Position minx, miny;

    *xp = child->core.x;		/* default to current values */
    *yp = child->core.y;
    *widthp = child->core.width;
    *heightp = child->core.height;
    if (geomp) {			/* mix in any requested changes */
	if (geomp->request_mode & CWX) *xp = geomp->x;
	if (geomp->request_mode & CWY) *yp = geomp->y;
	if (geomp->request_mode & CWWidth) *widthp = geomp->width;
	if (geomp->request_mode & CWHeight) *heightp = geomp->height;
    }

    /*
     * Make sure that the child is at least as large as the porthole; there
     * is no maximum size.
     */
    if (*widthp < pw->core.width) *widthp = pw->core.width;
    if (*heightp < pw->core.height) *heightp = pw->core.height;

    /*
     * Make sure that the child is still on the screen.  Note that this must
     * be done *after* the size computation so that we know where to put it.
     */
    minx = ((Position) pw->core.width) - ((Position) *widthp);
    miny = ((Position) pw->core.height) - ((Position) *heightp);

    if (*xp < minx) *xp = minx;		/* keep at lower right corner */
    if (*yp < miny) *yp = miny;

    if (*xp > 0) *xp = 0;		/* keep at upper left corner */
    if (*yp > 0) *yp = 0;
}



/*****************************************************************************
 *                                                                           *
 *			 Porthole Widget Class Methods                       *
 *                                                                           *
 *****************************************************************************/


static void Realize (gw, valueMask, attributes)
    register Widget gw;
    Mask *valueMask;
    XSetWindowAttributes *attributes;
{
    attributes->bit_gravity = NorthWestGravity;
    *valueMask |= CWBitGravity;

    if (gw->core.width < 1) gw->core.width = 1;
    if (gw->core.height < 1) gw->core.height = 1;
    (*portholeWidgetClass->core_class.superclass->core_class.realize)
	(gw, valueMask, attributes);
}


static void Resize (gw)
    Widget gw;
{
    PortholeWidget pw = (PortholeWidget) gw;
    Widget child = find_child (pw);

    /*
     * If we have a child, we need to make sure that it is at least as big
     * as we are and in the right place.
     */
    if (child) {
	Position x, y;
	Dimension width, height;

	layout_child (pw, child, NULL, &x, &y, &width, &height);
	XtConfigureWidget (child, x, y, width, height, (Dimension) 0);
    }

    SendReport (pw, (unsigned int) (XawPRCanvasWidth | XawPRCanvasHeight));
}


static XtGeometryResult QueryGeometry (gw, intended, preferred)
    Widget gw;
    XtWidgetGeometry *intended, *preferred;
{
    register PortholeWidget pw = (PortholeWidget) gw;
    Widget child = find_child (pw);

    if (child) {
#define SIZEONLY (CWWidth | CWHeight)
	preferred->request_mode = SIZEONLY;
	preferred->width = child->core.width;
	preferred->height = child->core.height;

	if (((intended->request_mode & SIZEONLY) == SIZEONLY) &&
	    intended->width == preferred->width &&
	    intended->height == preferred->height)
	  return XtGeometryYes;
	else if (preferred->width == pw->core.width &&
		 preferred->height == pw->core.height)
	  return XtGeometryNo;
	else
	  return XtGeometryAlmost;
#undef SIZEONLY
    } 
    return XtGeometryNo;
}


static XtGeometryResult GeometryManager (w, req, reply)
    Widget w;
    XtWidgetGeometry *req, *reply;
{
    PortholeWidget pw = (PortholeWidget) w->core.parent;
    Widget child = find_child (pw);
    Boolean okay = TRUE;

    if (child != w) return XtGeometryNo;  /* unknown child */

    *reply = *req;			/* assume we'll grant everything */

    if ((req->request_mode & CWBorderWidth) && req->border_width != 0) {
	reply->border_width = 0;	/* require border width of 0 */
	okay = FALSE;
    }

    layout_child (pw, child, req, &reply->x, &reply->y,
		  &reply->width, &reply->height);

    if ((req->request_mode & CWX) && req->x != reply->x) okay = FALSE;
    if ((req->request_mode & CWY) && req->x != reply->x) okay = FALSE;
    if ((req->request_mode & CWWidth) && req->width != reply->width)
      okay = FALSE;
    if ((req->request_mode & CWHeight) && req->height != reply->height)
      okay = FALSE;


    /*
     * If we failed on anything, simply return without touching widget
     */
    if (!okay) return XtGeometryAlmost;

    /*
     * If not just doing a query, update widget and send report.  Note that
     * we will often set fields that weren't requested because we want to keep
     * the child visible.
     */
    if (!(req->request_mode & XtCWQueryOnly)) {
	unsigned int changed = 0;

	if (child->core.x != reply->x) {
	    changed |= XawPRSliderX;
	    child->core.x = reply->x;
	}
	if (child->core.y != reply->y) {
	    changed |= XawPRSliderY;
	    child->core.y = reply->y;
	}
	if (child->core.width != reply->width) {
	    changed |= XawPRSliderWidth;
	    child->core.width = reply->width;
	}
	if (child->core.height != reply->height) {
	    changed |= XawPRSliderHeight;
	    child->core.height = reply->height;
	}
	if (changed) SendReport (pw, changed);
    }

    return XtGeometryYes;		/* success! */
}


static void ChangeManaged (gw)
    Widget gw;
{
    PortholeWidget pw = (PortholeWidget) gw;
    Widget child = find_child (pw);	/* ignore extra children */

    if (child) {
	if (!XtIsRealized (gw)) {
	    XtWidgetGeometry geom, retgeom;

	    geom.request_mode = 0;
	    if (pw->core.width == 0) {
		geom.width = child->core.width;
		geom.request_mode |= CWWidth;
	    }
	    if (pw->core.height == 0) {
		geom.height = child->core.height;
		geom.request_mode |= CWHeight;
	    }
	    if (geom.request_mode &&
		XtMakeGeometryRequest (gw, &geom, &retgeom) ==
		XtGeometryAlmost) {
	        (void) XtMakeGeometryRequest (gw, &retgeom, NULL);
	    }
	}
	
	XtResizeWidget (child, Max (child->core.width, pw->core.width),
			Max (child->core.height, pw->core.height), 0);

	SendReport (pw, (unsigned int) XawPRAll);
    }
}
