/**
 *
 * $Id: DrawingArea.c,v 1.21 1998/05/04 22:04:36 rwscott Exp $
 *
 * Copyright (C) 1995 Free Software Foundation, Inc.
 *
 * This file is part of the GNU LessTif Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **/

static char rcsid[] = "$Id: DrawingArea.c,v 1.21 1998/05/04 22:04:36 rwscott Exp $";

#include <LTconfig.h>
#include <XmI/XmI.h>

#include <Xm/XmP.h>
#include <Xm/DrawingAP.h>
#include <Xm/TransltnsP.h>
#include <Xm/ScrolledWP.h>
#include <XmI/MacrosI.h>

#include <XmI/DebugUtil.h>

/* Forward Declarations */

static void class_initialize();

static void class_part_initialize(WidgetClass w_class);

static void initialize(Widget request, Widget new_w,
		       ArgList args, Cardinal *num_args);

static void resize(Widget w);

static void expose(Widget w, XEvent *event, Region region);

static XtGeometryResult query_geometry(Widget w,
				       XtWidgetGeometry *proposed,
				       XtWidgetGeometry *answer);

static Boolean set_values(Widget current, Widget request, Widget new_w,
			  ArgList args, Cardinal *num_args);

static XtGeometryResult geometry_manager(Widget w,
					 XtWidgetGeometry *request,
					 XtWidgetGeometry *reply);

static void changed_managed(Widget w);

static void PreferedSize(Widget w, Widget instig, XtWidgetGeometry *ir,
			 Dimension *wid, Dimension *hei);

static XmNavigability widget_navigable(Widget w);

/*
 * Resources for the Drawing Area class
 */
#define Offset(field) XtOffsetOf(XmDrawingAreaRec, drawing_area.field)
static XtResource resources[] =
{
    {
	XmNmarginWidth, XmCMarginWidth, XmRHorizontalDimension,
	sizeof(Dimension), Offset(margin_width),
	XmRImmediate, (XtPointer)10
    },
    {
	XmNmarginHeight, XmCMarginHeight, XmRVerticalDimension,
	sizeof(Dimension), Offset(margin_height),
	XmRImmediate, (XtPointer)10
    },
    {
	XmNresizeCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(resize_callback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNexposeCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(expose_callback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNinputCallback, XmCCallback, XmRCallback,
	sizeof(XtCallbackList), Offset(input_callback),
	XmRImmediate, (XtPointer)NULL
    },
    {
	XmNresizePolicy, XmCResizePolicy, XmRResizePolicy,
	sizeof(unsigned char), Offset(resize_policy),
	XmRImmediate, (XtPointer)XmRESIZE_ANY
    }
};

static XmSyntheticResource syn_resources[] =
{
    {
	XmNmarginWidth,
	sizeof(Dimension), Offset(margin_width),
	_XmFromHorizontalPixels, _XmToHorizontalPixels
    },
    {
	XmNmarginHeight,
	sizeof(Dimension), Offset(margin_height),
	_XmFromVerticalPixels, _XmToVerticalPixels
    }
};

void _XmDrawingAreaInput(Widget w, XEvent *event,
			 String *parems, Cardinal *num_params);

static XtActionsRec actions[] =
{
    {"DrawingAreaInput", _XmDrawingAreaInput},
};

/* *INDENT-OFF* */
static XmBaseClassExtRec _XmDrawingACoreClassExtRec = {
    /* next_extension            */ NULL,
    /* record_type               */ NULLQUARK,                             
    /* version                   */ XmBaseClassExtVersion,
    /* size                      */ sizeof(XmBaseClassExtRec),
    /* initialize_prehook        */ NULL,
    /* set_values_prehook        */ NULL,
    /* initialize_posthook       */ NULL,
    /* set_values_posthook       */ NULL,
    /* secondary_object_class    */ NULL,
    /* secondary_object_create   */ NULL,
    /* get_secondary_resources   */ NULL,
    /* fast_subclass             */ { 0 },
    /* get_values_prehook        */ NULL,
    /* get_values_posthook       */ NULL,
    /* class_part_init_prehook   */ NULL,
    /* class_part_init_posthook  */ NULL,
    /* ext_resources             */ NULL,
    /* compiled_ext_resources    */ NULL,
    /* num_ext_resources         */ 0,
    /* use_sub_resources         */ False,
    /* widget_navigable          */ widget_navigable,
    /* focus_change              */ NULL,
    /* wrapper_data              */ NULL
};

XmDrawingAreaClassRec xmDrawingAreaClassRec = {
    /* Core class part */
    {
	/* superclass            */ (WidgetClass) &xmManagerClassRec,
        /* class_name            */ "XmDrawingArea",
	/* widget_size           */ sizeof(XmDrawingAreaRec),
	/* class_initialize      */ class_initialize,
	/* class_part_initialize */ class_part_initialize,
	/* class_inited          */ False,
	/* initialize            */ initialize,
	/* initialize_hook       */ NULL,
	/* realize               */ XtInheritRealize,
	/* actions               */ actions,
	/* num_actions           */ XtNumber(actions),
	/* resources             */ resources,
	/* num_resources         */ XtNumber(resources),
	/* xrm_class             */ NULLQUARK,
	/* compress_motion       */ True,
	/* compress_exposure     */ XtExposeNoCompress,
	/* compress_enterleave   */ True,
	/* visible_interest      */ False,
	/* destroy               */ NULL,
	/* resize                */ resize,
	/* expose                */ expose,
	/* set_values            */ set_values,
	/* set_values_hook       */ NULL,
	/* set_values_almost     */ XtInheritSetValuesAlmost,
	/* get_values_hook       */ NULL,
	/* accept_focus          */ NULL,
	/* version               */ XtVersion,
	/* callback offsets      */ NULL,
	/* tm_table              */ _XmDrawingA_defaultTranslations,
	/* query_geometry        */ query_geometry,
	/* display_accelerator   */ NULL,
	/* extension             */ (XtPointer)&_XmDrawingACoreClassExtRec
    },
    /* Composite class part */
    {
	/* geometry manager */ geometry_manager, 
        /* change_managed   */ changed_managed, 
        /* insert_child     */ XtInheritInsertChild,
        /* delete_child     */ XtInheritDeleteChild,
        /* extension        */ (XtPointer)NULL
    },
    /* Constraint class part */
    {
	/* subresources      */ NULL,  
        /* subresource_count */ 0,     
        /* constraint_size   */ 0,     
        /* initialize        */ NULL,
        /* destroy           */ NULL,  
        /* set_values        */ NULL,  
        /* extension         */ NULL,  
    },
    /* XmManager class part */
    {
	/* translations                 */ _XmDrawingA_traversalTranslations,
        /* syn_resources                */ syn_resources,
        /* num_syn_resources            */ XtNumber(syn_resources),
        /* syn_constraint_resources     */ NULL,
        /* num_syn_constraint_resources */ 0,
        /* parent_process               */ XmInheritParentProcess,
	/* extension                    */ (XtPointer)NULL
    },
    /* XmDrawing Area part */
    {
	/* extension */ NULL,
    },
};
/* *INDENT-ON* */


WidgetClass xmDrawingAreaWidgetClass = (WidgetClass)&xmDrawingAreaClassRec;

static void
class_initialize()
{
    _XmDrawingACoreClassExtRec.record_type = XmQmotif;
}

static void
class_part_initialize(WidgetClass widget_class)
{
    XmDrawingAreaWidgetClass daclass = (XmDrawingAreaWidgetClass)widget_class;
    CompositeClassExtension ext, *extptr;

    extptr = (CompositeClassExtension *)_XmGetClassExtensionPtr(
		    (XmGenericClassExt *)&(daclass->composite_class.extension),
								   NULLQUARK);

    if (extptr == NULL || *extptr == NULL)
    {
	ext = (CompositeClassExtension)XtNew(CompositeClassExtensionRec);
	if (ext != NULL)
	{
	    ext->next_extension = daclass->composite_class.extension;
	    ext->record_type = NULLQUARK;
	    ext->version = XtCompositeExtensionVersion;
	    ext->record_size = sizeof(CompositeClassExtensionRec);
	    ext->accepts_objects = True;
#if XtSpecificationRelease >= 6
	    ext->allows_change_managed_set = True;
#endif
	    daclass->composite_class.extension = (XtPointer)ext;
	}
    }
    _XmFastSubclassInit(widget_class, XmDRAWING_AREA_BIT);
}

static void
initialize(Widget request, Widget new_w,
	   ArgList args, Cardinal *num_args)
{
    if (XtWidth(request) == 0)
    {
	XtWidth(new_w) = 2 * DA_MarginWidth(new_w);
    }

    if (XtHeight(request) == 0)
    {
	XtHeight(new_w) = 2 * DA_MarginHeight(new_w);
    }

    DEBUGOUT(XdbDebug(__FILE__, new_w,
		      "DrawingArea initialize: w/h %d %d\n",
		      XtWidth(new_w), XtHeight(new_w)));
}

static Boolean
set_values(Widget old, Widget request, Widget new_w,
	   ArgList args, Cardinal *num_args)
{
    Boolean need_refresh = False;

    DEBUGOUT(XdbDebug(__FILE__, new_w, "setvalues()\n"));

#define NE(x)	(x(old) != x(new_w))

    if (NE(DA_MarginHeight) ||
	NE(DA_MarginWidth) ||
	NE(DA_ResizePolicy))
    {
	need_refresh = True;
    }

    return need_refresh;
}

static void
expose(Widget w, XEvent *event, Region region)
{
    XmDrawingAreaCallbackStruct cb;

    DEBUGOUT(XdbDebug(__FILE__, w,
		      "DrawingArea expose: w/h %d %d\n",
		      XtWidth(w), XtHeight(w)));

    cb.reason = XmCR_EXPOSE;
    cb.event = event;
    cb.window = XtWindow(w);

    DEBUGOUT(XdbDebug(__FILE__, w, "DA_ExposeCallback\n"));

    XtCallCallbackList(w, DA_ExposeCallback(w), (XtPointer)&cb);

    _XmRedisplayGadgets(w, event, region);
}

static void
resize(Widget w)
{
    XmDrawingAreaCallbackStruct cb;

    cb.reason = XmCR_RESIZE;
    cb.event = NULL;
    cb.window = XtWindow(w);

    DEBUGOUT(XdbDebug(__FILE__, w, "DA_ResizeCallback\n"));

    XtCallCallbackList(w, DA_ResizeCallback(w), (XtPointer)&cb);
}

static void
PreferedSize(Widget w, Widget instigator, XtWidgetGeometry *ir,
	     Dimension *wid, Dimension *hei)
{
    int i, cnt;

    *wid = 0;
    *hei = 0;

    DEBUGOUT(XdbDebug(__FILE__, w,
		      "DrawingArea PreferredSize: w/h %d %d\n",
		      XtWidth(w), XtHeight(w)));

    if (DA_ResizePolicy(w) == XmRESIZE_NONE ||
	_XmGeoCount_kids((CompositeRec *)w) == 0)
    {
	*wid = XtWidth(w);
	*hei = XtHeight(w);

	return;
    }

    for (i = 0, cnt = 0; i < MGR_NumChildren(w); i++)
    {
	Widget child = MGR_Children(w)[i];
	XtWidgetGeometry child_geometry;

	if (!XtIsManaged(child))
	{
	    continue;
	}

	cnt++;

	/* we assume that the routines calling this one fill in the
	   width and height of the child responsible for the resize
	   request with their prefered size. */
	if (instigator == child)
	{
	    child_geometry = *ir;
	}
	else
	{
	    child_geometry.x = XtX(child);
	    child_geometry.y = XtY(child);
	    child_geometry.width = XtWidth(child);
	    child_geometry.height = XtHeight(child);
	    child_geometry.border_width = 0;
	}

	DEBUGOUT(XdbDebug2(__FILE__, w, child,
			   "Child wants %d %d\n",
			   child_geometry.width, child_geometry.height));

	if (child_geometry.x + child_geometry.width > *wid)
	{
	    *wid = child_geometry.x + child_geometry.width;
	}

	if (child_geometry.y + child_geometry.height > *hei)
	{
	    *hei = child_geometry.y + child_geometry.height;
	}
    }


    /*
     * What if we were to return 0 here ? That's not a valid width or height.
     * Let's see what happens if we return our current size instead in those
     * cases.
     */
    if (cnt == 0)
    {
	*wid = XtWidth(w);
	*hei = XtHeight(w);

	return;
    }

    /*
     * we assume that the X/Y coordinates of all the children have the
     * drawing area's margin width/height added to them.  But now we
     * increment the total width and height of the drawing area to
     * include the margins on the other side.
     */
    *wid += DA_MarginWidth(w);
    *hei += DA_MarginHeight(w);

    if (DA_ResizePolicy(w) == XmRESIZE_ANY)
    {
	return;
    }

    if (*wid < XtWidth(w))
    {
	*wid = XtWidth(w);
    }
    if (*hei < XtHeight(w))
    {
	*hei = XtHeight(w);
    }
}

static XtGeometryResult
query_geometry(Widget w, XtWidgetGeometry *proposed, XtWidgetGeometry *answer)
{
    Dimension wid, hei;
    XtWidgetGeometry geo;

#define	Wants(x)	((geo.request_mode & x) == x)

    DEBUGOUT(XdbDebug(__FILE__, w, "query_geometry\n"));

    geo = *proposed;

    answer->request_mode = 0;

    PreferedSize(w, NULL, NULL, &wid, &hei);

    answer->request_mode = CWWidth|CWHeight;
    answer->width = wid;
    answer->height = hei;

    if (Wants(CWHeight) && (geo.height != answer->height))
    {
	return XtGeometryNo;
    }
    if (Wants(CWWidth) && (geo.width != answer->width))
    {
	return XtGeometryNo;
    }

    return XtGeometryYes;
}

static XtGeometryResult
geometry_manager(Widget w,
		 XtWidgetGeometry *request,
		 XtWidgetGeometry *reply)
{
    Widget dw = XtParent(w), sw = XtParent(dw);
    XtGeometryResult result;
    Dimension wid = 0, hei = 0;
    XtWidgetGeometry req, preq;

    DEBUGOUT(XdbDebug2(__FILE__, dw, w, "geometry_manager(%s)\n",
		     (DA_ResizePolicy(dw) == XmRESIZE_NONE) ? "XmRESIZE_NONE" :
		     (DA_ResizePolicy(dw) == XmRESIZE_GROW) ? "XmRESIZE_GROW" :
	      (DA_ResizePolicy(dw) == XmRESIZE_ANY) ? "XmRESIZE_ANY" : "???"));

    if (XtIsSubclass(sw, xmScrolledWindowWidgetClass) &&
	SW_ScrollPolicy(sw) == XmAUTOMATIC &&
	(Widget)SW_ClipWindow(sw) == dw)
    {
	DEBUGOUT(XdbDebug2(__FILE__, dw, w,
			   "geometry_manager: in ScrolledWindow (%s) => YES\n",
			   XdbWidgetGeometry2String(request)));

	*reply = *request;

	if (!(reply->request_mode & CWX))
	{
	    reply->x = XtX(w);
	}
	if (!(reply->request_mode & CWY))
	{
	    reply->y = XtY(w);
	}
	if (!(reply->request_mode & CWWidth))
	{
	    reply->width = XtWidth(w);
	}
	if (!(reply->request_mode & CWHeight))
	{
	    reply->height = XtHeight(w);
	}
	if (!(reply->request_mode & CWBorderWidth))
	{
	    reply->border_width = XtBorderWidth(w);
	}

	/*
	 * Resize the widget as requested
	 * FIX ME : ScrolledWindow should do this for us
	 * MLM: Nah.  w is a child of dw, not of dw's ScrolledWindow
	 * parent.  *We're* supposed to do this
	 */
	XtX(w) = reply->x;
	XtY(w) = reply->y;
	XtWidth(w) = reply->width;
	XtHeight(w) = reply->height;
	XtBorderWidth(w) = reply->border_width;

	/* Trigger the ScrolledWindow's geometry algorithms so it can
	 * handle scrollbars and such more.
	 *
	 * Not using _XmMakeGeometryRequest here since this is a special case.
	 */
	DEBUGOUT(XdbDebug(__FILE__, dw, "XtMakeResizeRequest (phony) %d %d\n",
			  reply->width, reply->height));

	XtMakeResizeRequest(dw, reply->width, reply->height, NULL, NULL);

	return XtGeometryYes;
    }

    /* Normal case */
    req = *request;
    *reply = req;

    if (!(req.request_mode & CWX))
    {
	req.x = XtX(w);
    }
    if (!(req.request_mode & CWY))
    {
	req.y = XtY(w);
    }
    if (!(req.request_mode & CWWidth))
    {
	req.width = XtWidth(w);
    }
    if (!(req.request_mode & CWHeight))
    {
	req.height = XtHeight(w);
    }
    if (!(req.request_mode & CWBorderWidth))
    {
	req.border_width = XtBorderWidth(w);
    }

    /* Calculate needed size for DrawingArea widget */
    PreferedSize(dw, w, &req, &wid, &hei);

    switch (DA_ResizePolicy(dw))
    {
    case XmRESIZE_NONE:
	if ((wid > XtWidth(dw)) || (hei > XtHeight(dw)))
	{
	    DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryNo 1\n"));

	    return XtGeometryNo;
	}
	else
	{
	    /* the resize request doesn't require us to grow, accept it */
	    DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryYes 1\n"));

	    XtX(w) = req.x;
	    XtY(w) = req.y;
	    XtWidth(w) = req.width;
	    XtHeight(w) = req.height;
	    XtBorderWidth(w) = req.border_width;

	    return XtGeometryYes;
	}
	break;

    case XmRESIZE_GROW:
	if ((wid > XtWidth(dw)) || (hei > XtHeight(dw)))
	{
	    /* Attempt to resize DrawingArea widget */
	    preq.request_mode = (CWWidth | CWHeight);
	    preq.width = wid;
	    preq.height = hei;
	    result = _XmMakeGeometryRequest(dw, &preq);

	    if (result == XtGeometryYes)
	    {
		DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryYes 2\n"));

		XtX(w) = req.x;
		XtY(w) = req.y;
		XtWidth(w) = req.width;
		XtHeight(w) = req.height;
		XtBorderWidth(w) = req.border_width;

		return XtGeometryYes;
	    }
	    else
	    {
		DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryNo 2\n"));

		return XtGeometryNo;
	    }
	}
	else
	{
	    /* the resize request doesn't require us to grow, accept it */
	    DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryYes 3\n"));

	    XtX(w) = req.x;
	    XtY(w) = req.y;
	    XtWidth(w) = req.width;
	    XtHeight(w) = req.height;
	    XtBorderWidth(w) = req.border_width;

	    return XtGeometryYes;
	}
	break;

    case XmRESIZE_ANY:
	if ((wid != XtWidth(dw)) || (hei != XtHeight(dw)))
	{

	    preq.request_mode = (CWWidth | CWHeight);
	    preq.width = wid;
	    preq.height = hei;
	    result = _XmMakeGeometryRequest(dw, &preq);

	    if (result == XtGeometryYes)
	    {
		DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryYes 4\n"));

		XtX(w) = req.x;
		XtY(w) = req.y;
		XtWidth(w) = req.width;
		XtHeight(w) = req.height;
		XtBorderWidth(w) = req.border_width;

		return XtGeometryYes;
	    }
	    else
	    {
		DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryNo 3\n"));

		return XtGeometryNo;
	    }
	}
	else
	{
	    /* the resize request doesn't require us to alter our geometry,
	     * accept it */

	    DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryYes 5\n"));

	    XtX(w) = req.x;
	    XtY(w) = req.y;
	    XtWidth(w) = req.width;
	    XtHeight(w) = req.height;
	    XtBorderWidth(w) = req.border_width;

	    return XtGeometryYes;
	}
	break;
    }

    DEBUGOUT(XdbDebug(__FILE__, dw, "Return XtGeometryNo 4\n"));

    return XtGeometryNo;
}

static void
changed_managed(Widget w)
{
    Dimension wid, hei;
    Dimension reply_wid, reply_hei;
    XtGeometryResult result;
    XtWidgetGeometry req;

    /*
    _XmGMEnforceMargin(w, DA_MarginWidth(w), DA_MarginHeight(w), False);
    */

    if (DA_ResizePolicy(w) != XmRESIZE_NONE)
    {
	_XmGMEnforceMargin(w, DA_MarginWidth(w), DA_MarginHeight(w), False);
	PreferedSize(w, NULL, NULL, &wid, &hei);
	if (wid > XtWidth(w) || hei > XtHeight(w))
	{

	    req.request_mode = (CWWidth | CWHeight);
	    req.width = wid;
	    req.height = hei;
	    result = _XmMakeGeometryRequest(w, &req);
	    reply_wid = req.width;
	    reply_hei = req.height;

	}
	/* else we're not getting any bigger... */
	else if (DA_ResizePolicy(w) == XmRESIZE_ANY)
	{
	    req.request_mode = (CWWidth | CWHeight);
	    req.width = wid;
	    req.height = hei;
	    result = _XmMakeGeometryRequest(w, &req);
	    reply_wid = req.width;
	    reply_hei = req.height;
	}
    }
}

void
_XmDrawingAreaInput(Widget w,
		    XEvent *event,
		    String *parems,
		    Cardinal *num_params)
{
    XmDrawingAreaCallbackStruct cb;

    cb.reason = XmCR_INPUT;
    cb.event = event;
    cb.window = XtWindow(w);

    DEBUGOUT(XdbDebug(__FILE__, w, "DA_InputCallback\n"));

    XtCallCallbackList(w, DA_InputCallback(w), (XtPointer)&cb);
}

Widget
XmCreateDrawingArea(Widget parent, char *name,
		    Arg *arglist, Cardinal argcount)
{
    return XtCreateWidget(name, xmDrawingAreaWidgetClass, parent,
			  arglist, argcount);
}

static XmNavigability
widget_navigable(Widget w)
{
    if (XtSensitive(w) && MGR_TraversalOn(w))
    {
	if (MGR_NavigationType(w) == XmSTICKY_TAB_GROUP ||
	    MGR_NavigationType(w) == XmEXCLUSIVE_TAB_GROUP ||
	    (MGR_NavigationType(w) == XmTAB_GROUP && !_XmShellIsExclusive(w)))
	{
	    DEBUGOUT(XdbDebug(__FILE__, w,
			  "WidgetNavigable => XmDESCENDANTS_TAB_NAVIGABLE\n"));

	    /* Need to look at all children, too */
	    if (MGR_NumChildren(w) == 0)
	    {
		return XmTAB_NAVIGABLE;
	    }

	    return XmDESCENDANTS_TAB_NAVIGABLE;
	}
    }

    DEBUGOUT(XdbDebug(__FILE__, w, "WidgetNavigable => XmNOT_NAVIGABLE\n"));

    return XmNOT_NAVIGABLE;
}
