/* $Id: Plotter.c,v 1.1 91/05/20 15:14:54 gnb Exp $ */
/* $Source: /export/sources/x/At/Plotter/tmp/RCS/Plotter.c,v $ */

/*

Copyright 1990,1991 by the Massachusetts Institute of Technology

All rights reserved.

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, and that the name of the Massachusetts
Institute of Technology (M.I.T.) not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

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.

*/

/*
 * $Log:	Plotter.c,v $
 * Revision 1.1  91/05/20  15:14:54  gnb
 * Initial revision
 * 
 * 
 */

#include <math.h>
#include <assert.h>
#include <stdio.h>
#ifdef __STDC__
#include <stdlib.h>
#endif

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>    

#include <X11/IntrinsicP.h>	/* For the XtCheckSubclass def */

#ifdef _AtDevelopment_
#include "AtConverters.h"    
#include "Scale.h"    
#include "PlotterP.h"
#include "Axis.h"
#include "PlotP.h"
#else
#include <At/AtConverters.h>
#include <At/Scale.h>   
#include <At/PlotterP.h>
#include <At/Axis.h>
#include <At/PlotP.h>
#endif

    
#define NUMCHILDREN(w) (w->composite.num_children)
#define CHILD(w,i) (w->composite.children[i])
#define CONSTRAINT(w,i) ((AtPlotterConstraints)(CHILD(w,i)->core.constraints))
#define CONSTRAINTS(cw) ((AtPlotterConstraints)(((Widget)(cw))->core.constraints))

#define NTHCHILDISDISPLAYED(pw,i) (CONSTRAINT(pw, i)->plotter.displayed)
#define ISDISPLAYED(cw) (CONSTRAINTS(cw)->plotter.displayed)
#define USESY2AXIS(cw) (CONSTRAINTS(cw)->plotter.use_y2_axis)

/* The AtPlotterWidget parent of the child OBJECT cw */
#define ParentPlotter(cw) ((AtPlotterWidget)XtParent((Widget)(cw)))


#define AtWarning(w,msg) XtWarning(msg)

/* Shouldn't need this in R4.  Put in because XtNewString(NULL) dumps core
   on the DECstation. */
#define AtNewString(str) ((str) != NULL ? \
			  (strcpy(XtMalloc((unsigned)strlen(str) + 1), str)) : NULL)

#define SelectNONE 0
#define SelectREGION 1
#define SelectLEGEND 2

static void ReRankOrderChildren P((Widget));
static void RankOrderChildren P((Widget));
static void RankOrderRemove P((Widget));
static void PlotterError P((AtPlotterWidget, char *));

static int RecalcLegend P((AtPlotterWidget w));
static void RedrawLegend P((AtPlotterWidget w, Region r));

static void StartSelection P((AtPlotterWidget, XButtonPressedEvent *));
static void Drag P((AtPlotterWidget, XMotionEvent *));
static void EndSelection P((AtPlotterWidget, XMotionEvent *));
static void CancelSelection P((AtPlotterWidget, XMotionEvent *));
static void HandleMotion P((AtPlotterWidget, XMotionEvent *));

/*  Default translation table and action list  */

static char defaultTranslations[] =
    "<Btn1Down>:       start-selection() \n\
     <Btn1Motion>:     drag() \n\
     <Btn1Up>:         end-selection() \n\
     <Btn3Down>:       cancel-selection() \n\
     <Key>Escape:      cancel-selection() \n\
     <Motion>:         motion-notify()";

static XtActionsRec actions[] =
{
  { "motion-notify", (XtActionProc) HandleMotion },
  { "start-selection", (XtActionProc) StartSelection },
  { "drag", (XtActionProc) Drag },
  { "end-selection", (XtActionProc) EndSelection},
  { "cancel-selection", (XtActionProc) CancelSelection},
};
    
#define offset(field) XtOffset(AtPlotterWidget,field)
static XtResource plotter_resources[] = {
  {XtNfontFamily, XtCFontFamily, XtRString,
     sizeof(String), offset(plotter.font_family),
     XtRString, "new century schoolbook"},
  {XtNtitle, XtCTitle, XtRString,
     sizeof(String), offset(plotter.title), XtRString, NULL},
  {XtNlegendTitle, XtCLegendTitle, XtRString,
     sizeof(String), offset(plotter.legend_title), XtRString, "Legend"},
  {XtNtitleSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.title_size),
     XtRImmediate, (caddr_t)AtFontBIG},
  {XtNlegendTitleSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.legend_title_size),
     XtRImmediate, (caddr_t)AtFontNORMAL},
  {XtNlegendSize, XtCFontSize, XtRFontSize,
     sizeof(int), offset(plotter.legend_size),
     XtRImmediate, (caddr_t)AtFontNORMAL},
  {XtNtitleColor, XtCForeground, XtRPixel, sizeof(Pixel),
     offset(plotter.title_color), XtRString, (caddr_t)XtDefaultForeground},
  {XtNlegendColor, XtCForeground, XtRPixel, sizeof(Pixel),
     offset(plotter.legend_color), XtRString, (caddr_t)XtDefaultForeground},
  {XtNautoScale, XtCAutoScale, XtRBoolean,
     sizeof(Boolean), offset(plotter.auto_scale), XtRImmediate, (caddr_t)True},
  {XtNforceSquare, XtCForceSquare, XtRBoolean,
     sizeof(Boolean), offset(plotter.force_square), 
     XtRImmediate,(caddr_t)False},
  {XtNforceAspect, XtCForceAspect, XtRBoolean, sizeof(Boolean),
     offset(plotter.force_aspect), XtRImmediate, (caddr_t)False},
  {XtNfloatingX, XtCFloatingAxes, XtRBoolean,
     sizeof(Boolean), offset(plotter.floating_x), 
     XtRImmediate, (caddr_t)False},
  {XtNfloatingY, XtCFloatingAxes, XtRBoolean,
     sizeof(Boolean), offset(plotter.floating_y), 
     XtRImmediate, (caddr_t)False},
  {XtNframedAxes, XtCFramedAxes, XtRBoolean,
     sizeof(Boolean), offset(plotter.framed_axes), 
     XtRImmediate,(caddr_t)False},
  {XtNshowLegend, XtCShowLegend, XtRBoolean,
     sizeof(Boolean), offset(plotter.show_legend), 
     XtRImmediate, (caddr_t)True},
  {XtNlegendWidth, XtCLegendWidth, XtRShort,
     sizeof(short), offset(plotter.default_legend_width), 
     XtRImmediate, (caddr_t)0},
  {XtNlegendSpacing, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.legend_spacing), XtRImmediate, (caddr_t) 3},
  {XtNmarginWidth, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.margin_width), XtRImmediate, (caddr_t)3},
  {XtNmarginHeight, XtCMargin, XtRShort,
     sizeof(short), offset(plotter.margin_height), XtRImmediate, (caddr_t)3},
  {XtNrankChildren, XtCRankChildren, XtRBoolean, sizeof(Boolean), 
     offset(plotter.rank_children), XtRImmediate, (caddr_t)False},
  {XtNdoubleClickInterval, XtCDoubleClickInterval, XtRInt, sizeof(int),
     offset(plotter.double_click_interval), XtRImmediate, (caddr_t)250},
  {XtNmotionCallback, XtCCallback, XtRCallback,sizeof(XtCallbackList),
     offset(plotter.motion_callback), XtRCallback, NULL},
  {XtNclickCallback, XtCCallback, XtRCallback,sizeof(XtCallbackList),
     offset(plotter.click_callback), XtRCallback, NULL},
  {XtNdragCallback, XtCCallback, XtRCallback,sizeof(XtCallbackList),
     offset(plotter.drag_callback), XtRCallback, NULL},
  {XtNselectCallback, XtCCallback, XtRCallback,sizeof(XtCallbackList),
     offset(plotter.select_callback), XtRCallback, NULL},
  {XtNdoubleSelectCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
     offset(plotter.double_select_callback), XtRCallback, NULL},
  {XtNerrorCallback, XtCCallback, XtRCallback,sizeof(XtCallbackList),
     offset(plotter.error_callback), XtRCallback, NULL}
};
#undef offset

/*
 * The default bounding box is a nonsensical one, with xmin > xmax.
 * Unless it is modified to a sensible value, auto_scale() will ignore it.
 * This is so that TextPlots (annotations) and similar plot types
 * won't be involved in the autoscale computations.
 */
static BoundingBox default_bounding_box = {1.0, 0.0, 0.0, 0.0};

#define offset(field) XtOffset(AtPlotterConstraints,field)
static XtResource constraint_resources[] = {
  {XtNdisplayed, XtCDisplayed, XtRBoolean,
     sizeof(Boolean), offset(plotter.displayed),
     XtRImmediate, (caddr_t)True},
  {XtNlegendName, XtCLegendName, XtRString,
     sizeof(String), offset(plotter.legend_name),
     XtRString, NULL},
  {XtNuseY2Axis, XtCUseY2Axis, XtRBoolean,
     sizeof(Boolean), offset(plotter.use_y2_axis),
     XtRImmediate, (caddr_t)False},
  {XtNrankOrder, XtCRankOrder, XtRInt, sizeof(int), 
     offset(plotter.rank_order), XtRImmediate, (caddr_t) 0},
};
#undef offset

static void ClassInitialize P(());
static void Initialize P((AtPlotterWidget, AtPlotterWidget));
static void Destroy P((AtPlotterWidget));
static void Resize P((AtPlotterWidget));
static void Realize P((AtPlotterWidget, XtValueMask *,XSetWindowAttributes *));
static void Redisplay P((AtPlotterWidget, XEvent *, Region));
static Boolean SetValues P((AtPlotterWidget, AtPlotterWidget,
			    AtPlotterWidget));
static void InsertChild P((Widget));
static void DeleteChild P((Widget));
static void ConstraintInitialize P((Widget, Widget));
static void ConstraintDestroy P((Widget));
static Boolean ConstraintSetValues P((Widget, Widget, Widget));
static void AxisErrorCB P((AtAxisObject, AtPlotterWidget, String));

#define superclass (&constraintClassRec)

externaldef(compositeclassrec) AtPlotterClassRec atPlotterClassRec = {
  { /******* CoreClassPart *******/
    /* superclass           */  (WidgetClass) superclass,
    /* class_name           */  "AtPlotter",
    /* widget_size          */  sizeof(AtPlotterRec),
    /* class_initialize     */  (XtProc)ClassInitialize,
    /* class_part_initialize*/  NULL,
    /* class_inited         */  FALSE,
    /* initialize           */  (XtInitProc)Initialize,
    /* initialize_hook      */  NULL,
    /* realize              */  (XtRealizeProc)Realize,
    /* actions              */  actions,
    /* num_actions          */  XtNumber(actions),
    /* resources            */  plotter_resources,
    /* num_resources        */  XtNumber(plotter_resources),
    /* xrm_class            */  NULLQUARK,
    /* compress_motion      */  FALSE,
#ifdef X11R4_INTRINSICS
    /* compress_exposure    */  XtExposeCompressMaximal |
                                XtExposeGraphicsExposeMerged,
#else
    /* compress_exposure    */  TRUE,
#endif
    /* compress_enterleave  */  TRUE,
    /* visible_interest     */  FALSE,
    /* destroy              */  (XtWidgetProc)Destroy,
    /* resize               */  (XtWidgetProc)Resize,
    /* expose               */  (XtExposeProc)Redisplay,
    /* set_values           */  (XtSetValuesFunc) SetValues,
    /* set_values_hook      */  NULL,
    /* set_values_almost    */  XtInheritSetValuesAlmost,
    /* get_values_hook      */  NULL,
    /* accept_focus         */  NULL,
    /* version              */  XtVersion,
    /* callback_offsets     */  NULL,
    /* tm_table             */  defaultTranslations,
    /* query_geometry       */  NULL,
    /* display_accelerator  */  NULL,
    /* extension            */  NULL
  },
  { /**** CompositeClassPart ****/
    /* geometry_handler     */  NULL,
    /* change_managed       */  NULL,
    /* insert_child         */  (XtWidgetProc)InsertChild,
    /* delete_child         */  (XtWidgetProc)DeleteChild,
    /* extension            */  NULL
  },
  { /**** ConstraintClassPart ****/
    /* resources            */  constraint_resources,
    /* num_resources        */  XtNumber(constraint_resources),
    /* constraint_size      */  sizeof(AtPlotterConstraintsRec),
    /* initialize           */  (XtInitProc) ConstraintInitialize,
    /* destroy              */  (XtWidgetProc) ConstraintDestroy,
    /* set_values           */  (XtSetValuesFunc) ConstraintSetValues,
    /* extension            */  NULL,
  },
  { /**** AtPlotterClassPart ****/
    /* meaningless field    */  0
  }
};

WidgetClass atPlotterWidgetClass = (WidgetClass) &atPlotterClassRec;


static void ClassInitialize()
{
#ifdef X11R4_INTRINSICS
  static CompositeClassExtensionRec ext;

  ext.next_extension = NULL;
  ext.record_type = NULLQUARK;
  ext.version = XtCompositeExtensionVersion;
  ext.record_size = sizeof(CompositeClassExtensionRec);
  ext.accepts_objects = True;
  atPlotterClassRec.composite_class.extension = (XtPointer) &ext;
#endif    
  AtRegisterDoubleConverter();
  AtRegisterFontSizeConverter();
  AtRegisterTransformConverter();
}

static void GetTitle P((AtPlotterWidget w)); 
static void GetTitle(w)
AtPlotterWidget w;
{
  if (w->plotter.title != NULL) 
    w->plotter.title_text = 
      AtTextCreate(w->plotter.title, w->plotter.ff,
		   w->plotter.title_size);
  else
    w->plotter.title_text = NULL;
}

static void FreeTitle P((AtPlotterWidget w)); 
static void FreeTitle(w)
AtPlotterWidget w;
{
  if (w->plotter.title_text) AtTextDestroy(w->plotter.title_text);
  w->plotter.title_text = NULL;
}

static void GetLegendTitle P((AtPlotterWidget w)); 
static void GetLegendTitle(w)
AtPlotterWidget w;
{
  if (w->plotter.legend_title != NULL) 
    w->plotter.legend_title_text = 
      AtTextCreate(w->plotter.legend_title, w->plotter.ff,
		   w->plotter.legend_title_size);
  else
    w->plotter.legend_title_text = NULL;
}

static void FreeLegendTitle P((AtPlotterWidget w)); 
static void FreeLegendTitle(w)
AtPlotterWidget w;
{
  if (w->plotter.legend_title_text) 
    AtTextDestroy(w->plotter.legend_title_text);
  w->plotter.legend_title_text = NULL;
}



static void GetLegendText P((AtPlotterConstraints c, AtPlotterWidget p));
static void GetLegendText(c, p)
AtPlotterConstraints c;
AtPlotterWidget p;
{
  if (c->plotter.legend_name != NULL) {
    c->plotter.legend_name = 
      strcpy(XtMalloc(strlen(c->plotter.legend_name)+1),
				 c->plotter.legend_name);
    c->plotter.legend_text = AtTextCreate(c->plotter.legend_name,
					 p->plotter.ff,
					 p->plotter.legend_size);
  }
  else
    c->plotter.legend_text = NULL;
}

static void FreeLegendText P((AtPlotterConstraints cur,
			      AtPlotterConstraints new));
static void FreeLegendText(cur, new)
AtPlotterConstraints cur, new;
{
  if (cur->plotter.legend_name != NULL)
    XtFree(cur->plotter.legend_name);
  if (new->plotter.legend_text != NULL) {
    AtTextDestroy(new->plotter.legend_text);
    new->plotter.legend_text = NULL;
  }
}

/************************************************************************
 *
 *  Initialize
 *     The main widget instance initialization routine.
 *
 ************************************************************************/

static void Initialize P((AtPlotterWidget request,
			  AtPlotterWidget new));
static void Initialize (request, new)
AtPlotterWidget request, new;
{
  XGCValues gcv;
  Arg al[10];
  int ac;

  /* make private copies of string resource */
  new->plotter.font_family = AtNewString(new->plotter.font_family);
  new->plotter.title = AtNewString(new->plotter.title);
  new->plotter.legend_title = AtNewString(new->plotter.legend_title);

  new->plotter.ff = AtFontFamilyGet(XtDisplay(new), 
				    new->plotter.font_family);
  GetTitle(new);
  GetLegendTitle(new);

  gcv.foreground = new->plotter.title_color;
  new->plotter.titleGC = XtGetGC((Widget)new, GCForeground, &gcv);
  
  gcv.foreground = new->plotter.legend_color;
  new->plotter.legendGC = XtGetGC((Widget)new, GCForeground, &gcv);

  gcv.foreground = new->core.background_pixel ^ new->plotter.title_color;
  gcv.function = GXxor;
  new->plotter.dragGC = XtGetGC((Widget)new, GCForeground | GCFunction, &gcv);

  new->plotter.xaxis = (AtAxisObject)
    XtVaCreateWidget("xaxis", atAxisObjectClass, (Widget)new,
			    XtNvertical, False, NULL);

  new->plotter.yaxis = (AtAxisObject)
    XtCreateWidget("yaxis", atAxisObjectClass, (Widget)new, NULL, 0);

  ac = 0;
  XtSetArg(al[ac], XtNmirror, True); ac++;

  /*
   * Check the "sub" resources for y2 axis and see if it is displayed...
   * (This is needed so we can emulate the old default behaviour of X
   * and Y axes displayed, Y2 not displayed.  We check the resource
   * file before we create y2axis so we can override the default True
   * if we need to!)
   */
  {
    static Boolean disp;
    static XtResource disp_res = {
      XtNdisplayed, XtCDisplayed, XtRBoolean, sizeof(Boolean), 0,
      XtRImmediate, (caddr_t)False }; /* NB - default = FALSE!!! */

    XtGetSubresources((Widget)new, &disp, "y2axis", "AtAxis", &disp_res, 1,
		      NULL, 0);
    if (!disp) {
      XtSetArg(al[ac], XtNdisplayed, False);
      ac++;
    }
  }
  new->plotter.y2axis = (AtAxisObject)
    XtCreateWidget("y2axis", atAxisObjectClass, (Widget)new, al, ac); 

  ac = 0;
  XtSetArg(al[ac], XtNmirror, True); ac++;
  XtSetArg(al[ac], XtNautoNumber, False); ac++;
  XtSetArg(al[ac], XtNvertical, False); ac++;
  new->plotter.xframeaxis = (AtAxisObject)
    XtCreateManagedWidget("xframeaxis", atAxisObjectClass, (Widget)new, al, ac);

  ac--;				/* y axes are not vertical! */
  new->plotter.yframeaxis = (AtAxisObject)
    XtCreateManagedWidget("yframeaxis", atAxisObjectClass, (Widget)new, al, ac);

  /* Y2 frame is neither mirrored nor vertical! */
  new->plotter.y2frameaxis = (AtAxisObject)
    XtVaCreateManagedWidget("y2frameaxis", atAxisObjectClass, (Widget)new,
			    XtNautoNumber, False, NULL);

  if (NUMCHILDREN(new) != FIRST_PUBLIC_CHILD || 
      new->plotter.y2frameaxis != (AtAxisObject)CHILD(new, CHILD_Y2FRAMEAXIS)) {
    PlotterError(new, 
		 "AtPlotter Initialize: Private children in public spaces!");
  }
  
  XtAddCallback((Widget)new->plotter.xaxis, XtNerrorCallback,
		(XtCallbackProc)AxisErrorCB, (XtPointer)new);
  XtAddCallback((Widget)new->plotter.yaxis, XtNerrorCallback,
		(XtCallbackProc)AxisErrorCB, (XtPointer)new);
  XtAddCallback((Widget)new->plotter.y2axis, XtNerrorCallback,
		(XtCallbackProc)AxisErrorCB, (XtPointer)new);

  new->plotter.clickx = new->plotter.clicky = 0;
  new->plotter.dragwidth = new->plotter.dragheight = 0;
  new->plotter.lastclick = CurrentTime;  /* a magic time value */
  new->plotter.dragging = False;
  new->plotter.selection_mode = SelectNONE;
  new->plotter.legend_item = -1;
  new->plotter.ordered_children = NULL;

  new->plotter.rescale_required = new->plotter.layout_required = True;
}

/***********************************************************************
 *
 * A set of helper routines for the redraw protocol.
 *
 * The first few of these are local to this file, the last few are the
 * routines that plotwidget children use to request redraws.
 * 
 */

/* Request a synthetic expose event if none has happened yet */
static void RequestSyntheticExpose P((AtPlotterWidget pw)); 
static void RequestSyntheticExpose(pw)
AtPlotterWidget pw;
{
  XExposeEvent ev;

  if (!pw->plotter.expose_requested && XtWindow(pw)) {
    ev.type = Expose;
    ev.display = XtDisplay(pw);
    ev.window = XtWindow(pw);
    ev.x = ev.y = ev.width = ev.height = ev.count = 0; 
    XSendEvent(XtDisplay(pw), XtWindow(pw), False, 0, (XEvent *)&ev);
    pw->plotter.expose_requested = ER_SYNTH;
  }
}

/* Request a full expose event with a clear screen */
static void RequestFullExpose P((AtPlotterWidget pw)); 
static void RequestFullExpose(pw)
AtPlotterWidget pw;
{
  if (!XtWindow(pw)) return;
  
  switch ((pw->plotter.expose_requested & ER_MASK)) {
  case ER_NONE:
    XClearArea(XtDisplay(pw), XtWindow(pw), 0, 0, 0, 0, True);
    pw->plotter.expose_requested = ER_PHYS;
    break;
  case ER_SYNTH:
    pw->plotter.expose_requested |= ER_NEEDCLEAR;
    break;
  default:
    /* Is alrady had a physical expose requested */
    break;
  }
}

/* 
 * ExtendedList maintainence.  These are lists of from, to pairs for
 * each plot for areas requiring rescaling (due to plot extend calls).
 * An ExtendedList of from>to implies the whole plot, and these
 * routines take care of deleteing partials if there is a full rescale
 * present. 
 */
#define cwel (CONSTRAINTS(cw)->plotter.extended_list)

static void DestroyExtendedList P((AtPlotWidget cw)); 
static void DestroyExtendedList(cw)
AtPlotWidget cw;
{
  ExtendedList *c, *n;
  for (c = cwel; c; c = n) {
    n = c->next;
    XtFree((char *)c);
  }
  cwel = NULL;
}

static void AddExtendedList P((AtPlotWidget cw, int from, int to));
static void AddExtendedList(cw, from, to)
AtPlotWidget cw;
int from, to;
{
  ExtendedList *el = (ExtendedList *)XtMalloc(sizeof (ExtendedList));
  el->from = from;
  el->to = to;
  if (from > to) {
    /* Is a complete rescale, so forget any partials */
    DestroyExtendedList(cw); 
  } else if (cwel && cwel->from > cwel->to) {
    /* A complete rescale already exists, drop this one */
    XtFree((char *)el);
    return;
  }
  /* Either this is a complete rescale request or an additional
     partial one */
  el->next = cwel;
  cwel = el;
}

#undef cwel

#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))

/*
 * Calculate the new bounding box for the whole plot.  Return TRUE if
 * it changed, else FALSE.
 */
static Boolean NewBoundingBox P((AtPlotterWidget pw)); 
static Boolean NewBoundingBox(pw)
AtPlotterWidget pw;
{
  PlotterBoundingBox nbb;
  BoundingBox *cbbp;
  int i;
  Boolean ret;
  
  nbb.xmin = nbb.ymin = nbb.y2min = HUGE_VAL;
  nbb.xmax = nbb.ymax = nbb.y2max = -HUGE_VAL;
  
  for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
    if (NTHCHILDISDISPLAYED(pw, i)) {
      cbbp = &(CONSTRAINT(pw, i)->plotter.bounding_box);
      if (cbbp->xmin > cbbp->xmax) continue; /* Is a child w/o boundingbox */
      nbb.xmax = max(nbb.xmax, cbbp->xmax);
      nbb.xmin = min(nbb.xmin, cbbp->xmin);
      if (CONSTRAINT(pw, i)->plotter.use_y2_axis) {
	nbb.y2min = min(nbb.y2min, cbbp->ymin);
	nbb.y2max = max(nbb.y2max, cbbp->ymax);
      } else {
	nbb.ymin = min(nbb.ymin, cbbp->ymin);
	nbb.ymax = max(nbb.ymax, cbbp->ymax);
      }
    }
  }
#define dif(fld) (nbb.fld != pw->plotter.bounding_box.fld)
  ret = dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax) || 
    dif(y2min) || dif(y2max);
#undef dif
  
  pw->plotter.bounding_box = nbb;
  return ret;
}

/*
 * Merge the second boundingbox into the first, return True is the bb changed.
 * 
 */
static Boolean MergeBoundingBox P((BoundingBox *ob, BoundingBox *nb));
static Boolean MergeBoundingBox(ob, nb)
     BoundingBox *ob, *nb;
{
  BoundingBox old;

  old = *ob;
  ob->xmax = max(ob->xmax, nb->xmax);
  ob->xmin = max(ob->xmin, nb->xmin);
  ob->ymax = max(ob->ymax, nb->ymax);
  ob->ymin = max(ob->ymin, nb->ymin);
#define dif(fld) (old.fld != ob->fld)
  return dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax);
#undef dif
}

/*********************************************************************
 * 
 * These routines are called by children as well as the parentto
 * request redrawing as appropriate.
 * 
 */
void AtPlotterPlotExtended(cw, bb, from, to)
AtPlotWidget cw;
BoundingBox *bb;
int from, to;
{
  AtPlotterWidget pw = ParentPlotter(cw);
  Boolean bb_changed;
  
  XtCheckSubclass((Widget)cw, atPlotWidgetClass, 
		  "AtPlotterPlotExtended requires an AtPlot widget");

  bb_changed = MergeBoundingBox(&CONSTRAINTS(cw)->plotter.bounding_box, bb);
  
  /* Don't bother if a rescale is already pending */
  if (ISDISPLAYED(cw) && !pw->plotter.rescale_required) {
    if (bb_changed && NewBoundingBox(pw)) {
      /* Overall bb has changed, so request an overall rescale */
      pw->plotter.rescale_required = True;
      RequestFullExpose(pw); 
    } else {
      /* This plot has been extended, but still fits on the graph */
      AddExtendedList(cw, from, to); 
      RequestSyntheticExpose(pw); 
    }
  }
}

/*
 * Refresh is set true if fast_update is on and we can erase the old
 * one without having to redraw everything.
 */
void AtPlotterPlotDataChanged(cw, bb, refresh)
AtPlotWidget cw;
BoundingBox *bb;
int refresh; 
{
  AtPlotterWidget pw = ParentPlotter(cw);

  XtCheckSubclass((Widget)cw, atPlotWidgetClass, 
		  "AtPlotterDataChanged requires an AtPlot widget");

  CONSTRAINTS(cw)->plotter.bounding_box = *bb;
  
  /* Don't bother if a rescale is already pending */
  if (ISDISPLAYED(cw) && !pw->plotter.rescale_required) {
    if (NewBoundingBox(pw)) {
      /* Overall bb has changed, so request an overall rescale */
      pw->plotter.rescale_required = True;
      RequestFullExpose(pw); 
    } else {
      /* This plot has been changed, but overall bb is the same.
	 Request a redraw and a rescale on the whole of this plot */
      AddExtendedList(cw, 0, -1);
      if (refresh) {
	AtPlotterRefreshRequired(cw); 
      } else {
	pw->plotter.redraw_required = True; 
	RequestSyntheticExpose(pw); 
      }
    } 
  }
}


void AtPlotterRefreshRequired(cw)
AtPlotWidget cw;
{
  if (!XtIsSubclass((Widget)cw, atPlotWidgetClass) && 
      !XtIsSubclass((Widget)cw, atAxisObjectClass))
    XtAppError(XtWidgetToApplicationContext((Widget)cw),
	       "AtPlotterRefreshRequired requires an AtPlot or AtAxis widget");
  
  if (ISDISPLAYED(cw))  {
    CONSTRAINTS(cw)->plotter.needs_refresh = True;
    RequestSyntheticExpose(ParentPlotter(cw)); 
  }
}

void AtPlotterRedrawRequired(cw)
AtPlotWidget cw;
{
  if (!XtIsSubclass((Widget)cw, atPlotWidgetClass) && 
      !XtIsSubclass((Widget)cw, atAxisObjectClass))
    XtAppError(XtWidgetToApplicationContext((Widget)cw),
	       "AtPlotterRedrawRequired requires an AtPlot or AtAxis widget");
  
  if (ISDISPLAYED(cw)) {
    ParentPlotter(cw)->plotter.redraw_required = True; 
    RequestFullExpose(ParentPlotter(cw));
  }
}

void AtPlotterLayoutRequired(cw)
AtPlotWidget cw;
{
  if (!XtIsSubclass((Widget)cw, atPlotWidgetClass) && 
      !XtIsSubclass((Widget)cw, atAxisObjectClass))
    XtAppError(XtWidgetToApplicationContext((Widget)cw),
	       "AtPlotterLayoutRequired requires an AtPlot or AtAxis widget");

  if (ISDISPLAYED(cw)) {
    ParentPlotter(cw)->plotter.layout_required = True; 
    RequestFullExpose(ParentPlotter(cw));
  }
}

void AtPlotterRescaleRequired(cw)
AtPlotWidget cw;
{
  if (!XtIsSubclass((Widget)cw, atPlotWidgetClass) && 
      !XtIsSubclass((Widget)cw, atAxisObjectClass))
    XtAppError(XtWidgetToApplicationContext((Widget)cw),
	       "AtPlotterRescaleRequired requires an AtPlot or AtAxis widget");

  if (ISDISPLAYED(cw)) {
    ParentPlotter(cw)->plotter.rescale_required = True; 
    RequestFullExpose(ParentPlotter(cw));
  }
}

/*
 * These private ones are called from this file and are passed the
 * parent widget
 */
static void RescaleRequired P((AtPlotterWidget pw)); 
static void RescaleRequired(pw)
AtPlotterWidget pw;
{
  pw->plotter.rescale_required = True; 
  RequestFullExpose(pw);
}

static void LayoutRequired P((AtPlotterWidget pw)); 
static void LayoutRequired(pw)
AtPlotterWidget pw;
{
  pw->plotter.layout_required = True; 
  RequestFullExpose(pw);
}

static void RedrawRequired P((AtPlotterWidget pw)); 
static void RedrawRequired(pw)
AtPlotterWidget pw;
{
  pw->plotter.redraw_required = True; 
  RequestFullExpose(pw);
}

/***********************************************************************
 *
 * The guts of the rescale / redraw / relayout code
 *
 * 1) Layout.  Recalculates all the layouts, returns TRUE if the size
 * or pixel position of any of the three axes changed.
 * 
 * XXX - assume the axes know enough to answer AtAxisWidth sensibly!
 * XXX - also assumes axis.max & axis.min are determined!
 * XXX - This includes the frame axes!!!!!
 * 
 */
static Boolean Layout P((AtPlotterWidget pw)); 
static Boolean Layout(pw)
AtPlotterWidget pw;
{
  PlotterLayout *lp = &pw->plotter.layout;
  PlotterLayout old;
  Boolean floatx, floaty;
  int xwid, ywid, y2wid;
  double xmin, xmax, ymin, ymax;
  Boolean set_bounds = False;

  old = pw->plotter.layout;
  
  /* The basic layout */
  lp->x1 = pw->plotter.margin_width;
  lp->y1 = pw->plotter.margin_height;
  lp->x2 = pw->core.width - 1 - pw->plotter.margin_width;
  lp->y2 = pw->core.height - 1 - pw->plotter.margin_height;

  /* Assume legend is on RHS at present */
  if (pw->plotter.show_legend) {
    lp->x2 -= lp->legend_width + pw->plotter.margin_width;
    lp->legend_x = lp->x2 + pw->plotter.margin_width;
  }

  /* Assume title at top at present */
  if (pw->plotter.title_text) {
    lp->title_y = lp->y1 + AtTextAscent(pw->plotter.title_text);
    lp->y1 += AtTextHeight(pw->plotter.title_text) +
      pw->plotter.margin_height;
  }

  /* Calculate the "width" of the axes */
  xwid = ywid = y2wid = 0;
  floatx = floaty = False;

  if (!pw->plotter.framed_axes && (pw->plotter.floating_x ||
				   pw->plotter.floating_y) ||
      pw->plotter.force_square || pw->plotter.force_aspect) {
    AtAxisGetBounds(pw->plotter.xaxis, &xmin, &xmax);
    AtAxisGetBounds(pw->plotter.yaxis, &ymin, &ymax);
  }

  if (!pw->plotter.framed_axes) {
    floatx = pw->plotter.floating_x && xmin < 0 && ymax > 0;
    floaty = pw->plotter.floating_y && ymin < 0 && xmax > 0;
  }
  
  if (ISDISPLAYED(pw->plotter.xaxis))
    xwid = AtAxisWidth(pw->plotter.xaxis);
  if (ISDISPLAYED(pw->plotter.yaxis))
    ywid = AtAxisWidth(pw->plotter.yaxis);
  if (ISDISPLAYED(pw->plotter.y2axis))
    y2wid = AtAxisWidth(pw->plotter.y2axis);

  if (!floatx) lp->y2 -= xwid;
  if (!floaty) lp->x1 += ywid;
  lp->x2 -= y2wid;

  if (pw->plotter.framed_axes) {
    lp->y1 += AtAxisWidth(pw->plotter.xframeaxis);
    if (!ISDISPLAYED(pw->plotter.yaxis)) 
      lp->x1 += AtAxisWidth(pw->plotter.y2frameaxis);
    if (!ISDISPLAYED(pw->plotter.y2axis)) 
      lp->x2 -= AtAxisWidth(pw->plotter.yframeaxis);
  }

  if (pw->plotter.force_square) {
    double xs, ys;
    /* Make x and (y or y2) axes have same pixel scale */
    if (!ISDISPLAYED(pw->plotter.yaxis)) 
      AtAxisGetBounds(pw->plotter.y2axis, &ymin, &ymax);
    
    xs = xmax - xmin;
    ys = ymax - ymin;
    if (xs > ys) {
      ymax = ymin + xs * lp->height / lp->width;
      AtAxisSetBounds(ISDISPLAYED(pw->plotter.yaxis) ?
		      pw->plotter.yaxis : pw->plotter.y2axis,
		      ymin, ymax);
      set_bounds = True; 
    }
    if (ys > xs) {
      xmax = xmin + ys * lp->width / lp->height;
      AtAxisSetBounds(pw->plotter.xaxis, xmin, xmax);
      set_bounds = True; 
    }
  } else if (pw->plotter.force_aspect) {
    /* Make aspect ratio the same by wasting space */
    double ar, p_ar; 

    if (!ISDISPLAYED(pw->plotter.yaxis)) 
      AtAxisGetBounds(pw->plotter.y2axis, &ymin, &ymax);

    ar = (xmax - xmin) / (ymax - ymin);
    p_ar = (double)lp->width / (double)lp->height;

    if (ar > p_ar) {
      /* With is too narrow, so shorten y axis */
      int new_height, extra_height;

      new_height = lp->width / ar;
      extra_height = lp->height - new_height;
      assert(extra_height >= 0);
      lp->y2 = lp->y1 + new_height;
      lp->height = new_height;
      /* Center it! */
      lp->y1 += extra_height >> 1;
      lp->y2 += extra_height >> 1;
    } else if (ar < p_ar) {
      /* Too short, narrow it */
      int new_width, extra_width;

      new_width = ar * lp->height;
      extra_width = lp->width - new_width;
      assert(extra_width >= 0);
      lp->x2 = lp->x1 + new_width;
      lp->width = new_width;
      lp->x1 += extra_width >> 1;
      lp->x2 += extra_width >> 1;
    }
  }
  
  lp->width = lp->x2 - lp->x1 + 1;
  lp->height = lp->y2 - lp->y1 + 1;

  if (pw->plotter.title_text) {
    lp->title_x = lp->x1 + 
      (lp->width - AtTextWidth(pw->plotter.title_text)) / 2;
  }

  if (pw->plotter.show_legend) {
    lp->legend_y = lp->y1 + (lp->height - lp->legend_height)/2;
  }
  
#ifdef DEBUG
  fprintf(stderr, "In Layout, from %d,%d to %d,%d\n", lp->x1, lp->y1,
	  lp->x2, lp->y2);
#endif 

#define dif(fld) (lp->fld != old.fld)
  return set_bounds || dif(x1) || dif(x2) || dif(y1) || dif(y2);
#undef dif
}

static void CopyFrameAxisBounds P((AtPlotterWidget pw)); 
static void CopyFrameAxisBounds(pw)
AtPlotterWidget pw;
{
  AtAxisCopyBounds(pw->plotter.xaxis, pw->plotter.xframeaxis);
  if (!ISDISPLAYED(pw->plotter.y2axis))
    AtAxisCopyBounds(pw->plotter.yaxis, pw->plotter.yframeaxis); 
  if (!ISDISPLAYED(pw->plotter.yaxis))
    AtAxisCopyBounds(pw->plotter.y2axis, pw->plotter.y2frameaxis); 
}

/*
 * Make the min & max of the three axis right; easy if we are not
 * autoscaling!!! Use the axis routines to do the autoscaling because
 * they know about rounding, overshoot, etc all of which is (will be)
 * a per axis resource.
 */
static void GetAxisScales P((AtPlotterWidget pw)); 
static void GetAxisScales(pw)
AtPlotterWidget pw;
{
  if (pw->plotter.auto_scale) {
    if (ISDISPLAYED(pw->plotter.xaxis)) {
      if (pw->plotter.bounding_box.xmax <=
	  pw->plotter.bounding_box.xmin) 
	AtAxisSetBounds(pw->plotter.xaxis, 0.0, 1.0);
      else
	AtAxisSetBounds(pw->plotter.xaxis, pw->plotter.bounding_box.xmin,
			pw->plotter.bounding_box.xmax);
    }
    if (ISDISPLAYED(pw->plotter.yaxis)) {
      if (pw->plotter.bounding_box.ymax <=
	  pw->plotter.bounding_box.ymin) 
	AtAxisSetBounds(pw->plotter.yaxis, 0.0, 1.0);
      else
	AtAxisSetBounds(pw->plotter.yaxis, pw->plotter.bounding_box.ymin,
			pw->plotter.bounding_box.ymax);
    }
    if (ISDISPLAYED(pw->plotter.y2axis)) {
      if (pw->plotter.bounding_box.y2max <=
	  pw->plotter.bounding_box.y2min) 
	AtAxisSetBounds(pw->plotter.y2axis, 0.0, 1.0);
      else
      AtAxisSetBounds(pw->plotter.y2axis, pw->plotter.bounding_box.y2min,
		      pw->plotter.bounding_box.y2max);
    }
  }
  /* Hmm.  May need frame axes to make the layout work */
  if (pw->plotter.framed_axes) {
    CopyFrameAxisBounds(pw); 
  }
}


/*
 * Actually redraw the entire plot
 */
static void Redraw P((AtPlotterWidget pw, Region region));
static void Redraw(pw, region)
AtPlotterWidget pw;
Region region;
{
  void (*drawfn) P((Display *, Window, AtPlotWidget, Region, int));
  int i;
  
  /* First, the title */
  if (pw->plotter.title_text) {
    if (region)
      XSetRegion(XtDisplay(pw), pw->plotter.titleGC, region);
    AtTextDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.titleGC,
	       pw->plotter.title_text, pw->plotter.layout.title_x,
	       pw->plotter.layout.title_y);
    if (region)
      XSetClipMask(XtDisplay(pw), pw->plotter.titleGC, None);
  }

  /* Now the legend */
  if (pw->plotter.show_legend) RedrawLegend(pw, region);

  /* Now the axes */
  if (ISDISPLAYED(pw->plotter.xaxis)) 
    AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.xaxis,
		pw->core.background_pixel, region);
  if (ISDISPLAYED(pw->plotter.yaxis)) 
    AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.yaxis,
		pw->core.background_pixel, region);
  if (ISDISPLAYED(pw->plotter.y2axis)) 
    AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.y2axis,
		pw->core.background_pixel, region);
  if (pw->plotter.framed_axes) {
    AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.xframeaxis,
	       pw->core.background_pixel, region);
    if (!ISDISPLAYED(pw->plotter.yaxis))
      AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.y2frameaxis,
		 pw->core.background_pixel, region);
    if (!ISDISPLAYED(pw->plotter.y2axis))
      AtAxisDraw(XtDisplay(pw), XtWindow(pw), pw->plotter.yframeaxis,
		 pw->core.background_pixel, region);
  }
  
  /*
   * Now the children, either in rank order or birth order
   */
  if (pw->plotter.rank_children) {
    Rank *rp;
    
    for (rp = pw->plotter.ordered_children; rp; rp = rp->next) {
      if (!ISDISPLAYED(rp->child)) continue;
      drawfn = 
	((AtPlotClassRec *)XtClass((Widget)(rp->child)))->plot_class.draw;
      if (drawfn)
	drawfn(XtDisplay(pw), XtWindow(pw), (AtPlotWidget)rp->child,
	       region, False);
    }
  } else {
    for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
      if (!NTHCHILDISDISPLAYED(pw, i)) continue;
      drawfn = ((AtPlotClassRec *)XtClass(CHILD(pw, i)))->plot_class.draw;
      if (drawfn)
	drawfn(XtDisplay(pw), XtWindow(pw), (AtPlotWidget)CHILD(pw, i), 
	       region, False);
    }
  }
}


/************************************************************************
 *
 *  Redisplay
 *     General redisplay function called on exposure events.
 *     THIS IS THE ONLY ROUTINE THAT DRAWS ON THE SCREEN!
 *
 *
 * The algorithm:
 *
 * Clear the screen if required.
 * If rescale is required, calculate min/max of axes (so layout knows
 *   how wide axes are); look at autoscale etc; (call scale callback)
 * Do global relayout if required. (Call layout callback)
 * Do global rescale if required (e.g. if relayout changed pixel size)
 *   Else rescale each plot according to extended_list, if any
 * Then do the redraw, clipped by the region (if any);
 *   Either a full redraw:
 *     title
 *     legend
 *     axes
 *     each displayed child in order(rank or birth)
 *   Or a refresh of each one if requested.
 * Forget all requested redraws/rescales/etc.
 * 
 ************************************************************************/

static void Redisplay P((AtPlotterWidget pw, XEvent *event, Region region));
static void Redisplay(pw, event, region)
AtPlotterWidget pw;
XEvent *event;
Region region;
{
#define ev ((XExposeEvent *)event)  
  Boolean pixels_moved = False;
  Boolean full_refresh = False;
  int i;
  
#ifdef DEBUG
  fprintf(stderr, 
	  "In redisplay, Reg = %lx event: synth %d x,y = %d,%d w,h = %d,%d\n",
	  region, ev->send_event, ev->x, ev->y, ev->width, ev->height);
#endif

  /* If the event covers (nearly) the whole window, ignore the region
     (for speed!) */
  if (ev->x < 10 && ev->y < 10 && ev->width > pw->core.width - 20 &&
      ev->height > pw->core.height - 20) {
    region = NULL;
    full_refresh = True;
  }

  if (ev->send_event) region = NULL; /* Is Synthetic! */
  
  /* First, clear the screen if that has been requested */
  if (pw->plotter.expose_requested & ER_NEEDCLEAR) {
    XClearWindow(XtDisplay(pw), XtWindow(pw));
    full_refresh = True; 
  }
  
  if (pw->plotter.rescale_required) {
    GetAxisScales(pw);
  }

  if (pw->plotter.layout_required) {
    pixels_moved = Layout(pw);
  }
  
  if (pixels_moved || pw->plotter.rescale_required) {
    PlotterLayout *lp = &pw->plotter.layout;

    /*
     *  With the y axes, must swap min and max values as window is
     * measured 0=top, we want 0 to be bottom.
     *
     * AtAxisSetLocation may do rounding etc that changed min and max
     * and this scale, so we need to do a global rescale after this.
     */ 
    if (ISDISPLAYED(pw->plotter.xaxis))
      AtAxisSetLocation(pw->plotter.xaxis, lp->x1, lp->y2, lp->x2,
		      lp->y2, lp->y2 - lp->y1);
    if (ISDISPLAYED(pw->plotter.yaxis))
      AtAxisSetLocation(pw->plotter.yaxis, lp->x1, lp->y2, lp->x1,
			lp->y1, lp->x2 - lp->x1);
    if (ISDISPLAYED(pw->plotter.y2axis))
      AtAxisSetLocation(pw->plotter.y2axis, lp->x2, lp->y2, lp->x2,
			lp->y1, lp->x2 - lp->x1);
    if (pw->plotter.framed_axes) {
      /* The setlocation may have moved endpointes etc. */
      AtAxisCopyTics(pw->plotter.xaxis, pw->plotter.xframeaxis); 
      AtAxisSetLocation(pw->plotter.xframeaxis, lp->x1, lp->y1, lp->x2,
		      lp->y1, lp->y2 - lp->y1);
      if (!ISDISPLAYED(pw->plotter.yaxis)) {
	AtAxisCopyTics(pw->plotter.y2axis, pw->plotter.y2frameaxis); 
	AtAxisSetLocation(pw->plotter.y2frameaxis, lp->x1, lp->y2, lp->x1,
			  lp->y1, lp->x2 - lp->x1);
      }
      if (!ISDISPLAYED(pw->plotter.y2axis)) {
	AtAxisCopyTics(pw->plotter.yaxis, pw->plotter.yframeaxis); 
	AtAxisSetLocation(pw->plotter.yframeaxis, lp->x2, lp->y2, lp->x2,
			  lp->y1, lp->x2 - lp->x1);
      }
    }
  }

  if (pixels_moved || pw->plotter.rescale_required) {
    /* Need to rescale the entire graph */
    for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
      void (*fn) P((AtPlotWidget, AtScale *, AtScale *, int, int));
      
      if (!NTHCHILDISDISPLAYED(pw, i)) continue;
      fn = ((AtPlotClassRec *) XtClass(CHILD(pw, i)))->plot_class.recalc;
      
      if (fn) 
	fn((AtPlotWidget)CHILD(pw, i), AtAxisGetScale(pw->plotter.xaxis), 
	   AtAxisGetScale(USESY2AXIS((AtPlotWidget)CHILD(pw, i)) ? 
			  pw->plotter.y2axis : pw->plotter.yaxis), 
	   0, -1);
    }
  } else {
    /* Not entire graph, perhaps individual chunks? */
    for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
      void (*fn) P((AtPlotWidget, AtScale *, AtScale *, int, int));
      ExtendedList *ep;
      
      if (!NTHCHILDISDISPLAYED(pw, i) || 
	  !(ep = CONSTRAINT(pw, i)->plotter.extended_list))
	continue;
      fn = ((AtPlotClassRec *) XtClass(CHILD(pw, i)))->plot_class.recalc;
      while (ep) {
	if (fn) 
	  fn((AtPlotWidget)CHILD(pw, i), AtAxisGetScale(pw->plotter.xaxis), 
	     AtAxisGetScale(USESY2AXIS((AtPlotWidget)CHILD(pw, i)) ?
			    pw->plotter.y2axis : pw->plotter.yaxis), 
	     ep->from, ep->to);
	ep = ep->next;
      }
    } 
  }

  if (pw->plotter.redraw_required || pixels_moved ||
      pw->plotter.rescale_required || region || full_refresh)
    Redraw(pw, region);		/* Redraw the whole lot */
  else {
    /* Perhaps one of the plots wants redrawing */
    /* Start with the axes */
    for (i = 0; i < FIRST_PUBLIC_CHILD; i++) {
	if (!NTHCHILDISDISPLAYED(pw, i)) continue;
	if (CONSTRAINT(pw, i)->plotter.needs_refresh)
	  AtAxisDraw(XtDisplay(pw), XtWindow(pw),
		     (AtAxisObject)CHILD(pw, i),
		     pw->core.background_pixel, region);
    }
    if (pw->plotter.rank_children) {
      Rank *rp;

      for (rp = pw->plotter.ordered_children; rp; rp = rp->next) {
	if (!ISDISPLAYED(rp->child)) continue;
	if (CONSTRAINTS((AtPlotWidget)rp->child)->plotter.needs_refresh) {
	  void (*fn) P((Display *, Window, AtPlotWidget, Region, int));
	  
	  if (fn = ((AtPlotClassRec *)
		    XtClass((Widget)(rp->child)))->plot_class.draw)
	    fn(XtDisplay(pw), XtWindow(pw), rp->child, region, True);
	}
      }
    } else {
      for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
	if (!NTHCHILDISDISPLAYED(pw, i)) continue;
	if (CONSTRAINT(pw, i)->plotter.needs_refresh) {
	  void (*fn) P((Display *, Window, AtPlotWidget, Region, int));
	  
	  if (fn = ((AtPlotClassRec *) XtClass(CHILD(pw, i)))->plot_class.draw)
	    fn(XtDisplay(pw), XtWindow(pw), (AtPlotWidget)CHILD(pw, i), 
	       region, True);
	}
      }
    }
  }
  
  /* Now forget all requests */
  pw->plotter.redraw_required = pw->plotter.rescale_required =
    pw->plotter.layout_required = False;
  pw->plotter.expose_requested = ER_NONE;
  
  for (i = 0; i < NUMCHILDREN(pw); i++) {
    AtPlotterConstraints c = CONSTRAINT(pw, i);

    c->plotter.needs_refresh = False;
    DestroyExtendedList((AtPlotWidget)CHILD(pw, i));
    c->plotter.extended_list = NULL;
  }
}


/************************************************************************
 *
 *  Destroy
 *	Clean up allocated resources when the widget is destroyed.
 *
 ************************************************************************/

static void Destroy (pw)
AtPlotterWidget pw;
{
  /* Free up the private data */
  FreeTitle(pw);
  FreeLegendTitle(pw);
  AtFontFamilyRelease(pw->plotter.ff);
  XtReleaseGC((Widget)pw, pw->plotter.titleGC);
  XtReleaseGC((Widget)pw, pw->plotter.legendGC);
  XtReleaseGC((Widget)pw, pw->plotter.dragGC);

  /* free our private copies of string resource */
  XtFree(pw->plotter.font_family);
  XtFree(pw->plotter.title);
  XtFree(pw->plotter.legend_title);

  /* free the linked list of ordered_children */
  if (pw->plotter.ordered_children) { 
    Rank *tmp = pw->plotter.ordered_children;
    while (tmp->next) {
      tmp = tmp->next;
      XtFree((char *)tmp->prev);
    }
    XtFree((char *)tmp);
  }

  return;
}

/************************************************************************
 *
 *  Resize
 *
 ************************************************************************/

static void Resize P((AtPlotterWidget pw)); 
static void Resize(pw)
AtPlotterWidget pw;
{
  if (XtWindow(pw)) {
    LayoutRequired(pw);
  }
}

/************************************************************************
 *
 *  Realize
 *
 ***********************************************************************/

static void Realize P((AtPlotterWidget w, XtValueMask *vm,
		    XSetWindowAttributes *wa));
static void Realize(w, vm, wa)
AtPlotterWidget w;
XtValueMask *vm;
XSetWindowAttributes *wa;
{
  (superclass->core_class.realize)((Widget)w, vm, wa);
  (void) NewBoundingBox(w);	/* Calculate initial boundingbox */
}

/************************************************************************
 *
 *  SetValues
 *
 ************************************************************************/

#define Changed(field) (new->plotter.field != current->plotter.field)

static Boolean SetValues(current, request, new)
AtPlotterWidget current, request, new;
{
  Boolean redraw = False;
  Boolean layout = False;
  Boolean recalc_legend = False;
  XGCValues gcv;
  int i;
  
  if (Changed(rank_children))
    redraw = True;
  
  if (Changed(auto_scale)) {
    if (new->plotter.auto_scale) (void)NewBoundingBox(new);
    RescaleRequired(new);
  }

  if (Changed(force_square) || Changed(force_aspect) ||
      Changed(floating_x) || Changed(floating_y)) { 
    layout = True;
  }

  if (Changed(framed_axes)) {
    layout = True;
    if (new->plotter.framed_axes) CopyFrameAxisBounds(new); 
  }
  
  if (Changed(font_family)) {
    XtFree(current->plotter.font_family);
    new->plotter.font_family = AtNewString(new->plotter.font_family);
    AtFontFamilyRelease(new->plotter.ff);
    new->plotter.ff = AtFontFamilyGet(XtDisplay(new),
				      new->plotter.font_family);
  }
  if (!Changed(title) && (Changed(font_family) || Changed(title_size)))
      AtTextReformat(new->plotter.title_text,
		     new->plotter.ff, new->plotter.title_size);

  if (!Changed(legend_title) && (Changed(font_family) ||
				 Changed(legend_title_size))) {
    AtTextReformat(new->plotter.legend_title_text,
		   new->plotter.ff, new->plotter.legend_title_size);
    recalc_legend = True; 
  }
    
  if (Changed(title)) {
    XtFree(current->plotter.title);
    new->plotter.title = AtNewString(new->plotter.title);
    FreeTitle(new);
    GetTitle(new);
    layout = True;
    redraw = True; 
  }

  if (Changed(legend_title)) {
    XtFree(current->plotter.legend_title);
    new->plotter.title = AtNewString(new->plotter.legend_title);
    FreeLegendTitle(new);
    GetLegendTitle(new);
    redraw = True;
    recalc_legend = True;
  }

  if (Changed(legend_size)) {
    for (i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(new); i++) {
      AtPlotterConstraints c = CONSTRAINT(new, i);

      FreeLegendText(c, c);
      GetLegendText(c, new);
    }
    recalc_legend = True;
  }

  if (Changed(title_color)) {
    XtReleaseGC((Widget)new, new->plotter.titleGC);
    gcv.foreground = new->plotter.title_color; 
    new->plotter.titleGC = XtGetGC((Widget)new, GCForeground, &gcv);
    redraw = True;
  }

  if (Changed(legend_color)) {
    XtReleaseGC((Widget)new, new->plotter.legendGC);
    gcv.foreground = new->plotter.title_color; 
    new->plotter.legendGC = XtGetGC((Widget)new, GCForeground, &gcv);
    redraw = True;
  }

  if (Changed(show_legend)) {
    if (new->plotter.show_legend) (void) RecalcLegend(new);
    layout = redraw = True;
  }

  if (new->plotter.show_legend && 
      (Changed(default_legend_width) || Changed(legend_spacing))) {
    recalc_legend = layout = redraw = True;
  }
  if (Changed(margin_width) || Changed(margin_height)) {
    layout = True;
  }

  /* Have looked at all the appropriate fields, so do the work! */
  
  if (recalc_legend) {
    if (RecalcLegend(new)) layout = True;
    else redraw = True;
  }
  
  if (layout) LayoutRequired(new);
  if (redraw) RedrawRequired(new);
  
  return False;
}

#undef Changed

static void InsertChild P((Widget w)); 
static void InsertChild(w)
Widget w;
{
  AtPlotterWidget p = ParentPlotter(w);

  /* warn if child of wrong class */
  if (!XtIsSubclass(w, atPlotWidgetClass) &&
      !XtIsSubclass(w, atAxisObjectClass))
    AtWarning(w, "Attempt to add child that is not a subclass of AtPlot");
    
  /* call the superclass's insert_child proc to actually add the child */
  (*superclass->composite_class.insert_child)(w);
  if (XtIsSubclass(w, atPlotWidgetClass)) {
    if (RecalcLegend(p)) LayoutRequired(p);
    RankOrderChildren(w);
    /*
     * maintain this list even if no rank order is requested, so if
     * rank order is wanted later the list is ready.
     */
    if (p->plotter.auto_scale && NewBoundingBox(p)) {
      RescaleRequired(p); 
    } else {
      RedrawRequired(p); 
    }
  }
}

static void DeleteChild P((Widget w)); 
static void DeleteChild(w)
Widget w;
{
  AtPlotterWidget p = ParentPlotter(w);
  int i;

  /* if a plot is selected, figure out which child is being deleted */
  /* and adjust the selected plot as necessary */
    
  if (XtIsSubclass(w, atPlotWidgetClass) && p->plotter.legend_item != -1) {
    for(i=FIRST_PUBLIC_CHILD; i<NUMCHILDREN(p); i++) 
      if (CHILD(p, i) == w) break;
    if (i == p->plotter.legend_item)
      p->plotter.legend_item = -1;
    else if (i < p->plotter.legend_item)
      p->plotter.legend_item -= 1;
  }

  /* call the superclass's delete_child proc to actually delete the child */
  (*superclass->composite_class.delete_child)(w);
  if (XtIsSubclass(w, atPlotWidgetClass)) {
    if (RecalcLegend(p)) LayoutRequired(p); 
    RankOrderRemove(w);
    if (p->plotter.auto_scale && NewBoundingBox(p)) {
      RescaleRequired(p); 
    } else {
      RedrawRequired(p); 
    }
  }
}


static void ConstraintInitialize(request, new)
Widget request, new;
{
  AtPlotterConstraints c = CONSTRAINTS((AtPlotWidget)new);
  AtPlotterWidget p = ParentPlotter(new);

  GetLegendText(c, p);
  c->plotter.bounding_box = default_bounding_box;
}


static void ConstraintDestroy(w)
Widget w;
{
  AtPlotterConstraints c = CONSTRAINTS((AtPlotWidget)w);
  FreeLegendText(c, c);
  DestroyExtendedList((AtPlotWidget)w); 
}

static Boolean ConstraintSetValues(current, request, new)
Widget current, request, new;
{
#define Changed(field) (newc->plotter.field != curc->plotter.field)    
  AtPlotterConstraints newc = (AtPlotterConstraints)new->core.constraints;
  AtPlotterConstraints curc =(AtPlotterConstraints)current->core.constraints;
  AtPlotterWidget parent = ParentPlotter(new);
  Boolean redraw = False;
  Boolean rescale = False;
  Boolean layout = False;
  
  if (Changed(displayed) || Changed(use_y2_axis)) {
    redraw = True;
    rescale = NewBoundingBox(parent);
    if (Changed(displayed) && XtIsSubclass(new, atAxisObjectClass)) {
      layout = True;
    }
  }
    
  if (Changed(legend_name)) {
    FreeLegendText(curc, newc);
    GetLegendText(newc, parent);
    layout = RecalcLegend(parent); 
  }

  if (Changed(rank_order))  {
    ReRankOrderChildren(new);
    if (parent->plotter.rank_children) redraw = True;
  }

  if (layout) LayoutRequired(parent);
  if (rescale) RescaleRequired(parent);
  if (redraw) RedrawRequired(parent);
  
#undef Changed
  return False; 
}

/*************************************************************
 **************            PlotterError          *************
 *************************************************************/
static void PlotterError P((AtPlotterWidget pw, char *string));
static void PlotterError(pw, string)
     AtPlotterWidget pw;
     char *string;
{
  if (pw->plotter.error_callback)
    XtCallCallbacks((Widget)pw, XtNerrorCallback, (caddr_t)string);
  else
    XtWarning(string);
}
    
static void AxisErrorCB P((AtAxisObject a, AtPlotterWidget pw, 
			   String errtext)); 
static void AxisErrorCB(a, pw, errtext)
     AtAxisObject a;
     AtPlotterWidget pw;
     String errtext;
{
  PlotterError(pw, errtext);
}

/*****************************************************************
 *
 * The routines for handling the legend
 *
 *
 * Recalculate the layout of the legend, return TRUE if the width changed.
 */
static int RecalcLegend P((AtPlotterWidget pw)); 
static int RecalcLegend(pw)
AtPlotterWidget pw;
{
  int i;
  AtPlotterConstraints c;
  int h = 0, w = 0;

  if (pw->plotter.show_legend == False) return FALSE;

  h = AtTextHeight(pw->plotter.legend_title_text);
  h += pw->plotter.margin_height;
  
  for(i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
    if (!NTHCHILDISDISPLAYED(pw, i)) continue; 
    c = CONSTRAINT(pw,i);
    if (i == pw->plotter.legend_item)
      pw->plotter.legend_item_y = h;
    if (c->plotter.legend_text != NULL) {
      h += AtTextHeight(c->plotter.legend_text) +
	pw->plotter.legend_spacing;
      w = max(w, AtTextWidth(c->plotter.legend_text));
    }
  }
  /* Add the with of the "icon" */
  w += 16 + 2 * pw->plotter.margin_width;
  
  w = max(w,AtTextWidth(pw->plotter.legend_title_text));
  
  if (pw->plotter.default_legend_width) w = pw->plotter.default_legend_width;
  
  i = w != pw->plotter.layout.legend_width;
  
  pw->plotter.layout.legend_height = h;
  pw->plotter.layout.legend_width = w;
  
  return i;
}

static void HighlightSelectedLegendItem P((AtPlotterWidget pw)); 
static void HighlightSelectedLegendItem(pw)
AtPlotterWidget pw;
{
  AtText *t;

  if (pw->plotter.legend_item != -1) {
    t = CONSTRAINT(pw,pw->plotter.legend_item)->plotter.legend_text;
    XFillRectangle(XtDisplay(pw), XtWindow(pw), pw->plotter.dragGC,
		   pw->plotter.layout.legend_x,
		   pw->plotter.legend_item_y + pw->plotter.layout.legend_y,
		   pw->plotter.layout.legend_width,
		   AtTextHeight(t) + pw->plotter.legend_spacing/2);
  }
}

static void RedrawLegend P((AtPlotterWidget w, Region region)); 
static void RedrawLegend(w, region)
AtPlotterWidget w;
Region region;
{
  int i;
  AtText *t;
  int y;

  if (w->plotter.show_legend == False) return;

  if (region) {
    XSetRegion(XtDisplay(w), w->plotter.legendGC, region);
  } 
  y = w->plotter.layout.legend_y;
  AtTextDrawJustified(XtDisplay(w), XtWindow(w), w->plotter.legendGC,
		      w->plotter.legend_title_text,
		      AtTextJUSTIFY_CENTER, AtTextJUSTIFY_CENTER,
		      w->plotter.layout.legend_x, y,
		      w->plotter.layout.legend_width,
		      AtTextHeight(w->plotter.legend_title_text));

  y += AtTextHeight(w->plotter.legend_title_text) + w->plotter.margin_width;
  for(i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(w); i++ ) {
    if (!NTHCHILDISDISPLAYED(w, i)) continue; 
    t = CONSTRAINT(w,i)->plotter.legend_text;
    if (t != NULL) {
      void (*f) P((Display *, Window, AtPlotWidget, int, int, int,
		   int, Region));

      AtTextDraw(XtDisplay(w), XtWindow(w), w->plotter.legendGC, t,
		 w->plotter.layout.legend_x+2*w->plotter.margin_width+16,
		 y + AtTextAscent(t));
	  
      if (f = ((AtPlotClassRec *) 
		XtClass(CHILD(w, i)))->plot_class.draw_icon)
	f(XtDisplay(w), XtWindow(w), (AtPlotWidget)CHILD(w, i),
	  w->plotter.layout.legend_x, y, 16, AtTextHeight(t), region);
      y += AtTextHeight(t) + w->plotter.legend_spacing;
    }
  }
  HighlightSelectedLegendItem(w);
  if (region) {
    XSetClipMask(XtDisplay(w), w->plotter.legendGC, None);
  } 
}


/*
 *                         Actions
 *
 * start-selection:
 *   store x,y coords
 *   if in plotting region
 *     selectionMode = plot
 *   if in legend region
 *     selectionMode = legend
 *     unhighlight any previous highlight legend entry
 *     highlight the appropriate legend entry, remember it
 *   else selectionMode = none
 *     unselect any highlight legend entries
 *
 * drag:
 *   if selectionMode = plot && still in plotting region
 *     if dragging
 *       erase old rectangle
 *     draw new rectangle
 *     dragging = True
 *   else if selectionMode = legend
 *     if no longer over the same entry
 *       unhighlight it, forget it
 *       if over a new entry
 *         highlight it, remember it
 *
 * end-selection:
 *   if selectionMode = plot
 *      if dragging
 *        erase rectangle
 *      if still inside plotting region
 *        if dragging && dragged > 4 pixels
 *          call dragCallback
 *        else
 *          call clickCallback
 *      dragging = False
 *   else if selectionMode = legend
 *      if there is a selected entry
 *         call selectCallback
 *         make it the primary selection
 *         leave the entry highlighted
 *   selectionMode = none
 *  
 * cancel-selection:
 *   if selectionMode == plot && dragging
 *     erase rectangle
 *   else if selectionMode = legend
 *     unhighlight any highlighted item
 *   selectionMode = none
 * 
 * motion-notify
 *   invoke motionCallback
 *
 */

#define InPlottingRegion(pw, event) \
    ((event->x >= pw->plotter.layout.x1) &&\
     (event->x <= pw->plotter.layout.x2) &&\
     (event->y >= pw->plotter.layout.y1) &&\
     (event->y <= pw->plotter.layout.y2))

#define InLegendRegion(pw,event) \
    ((pw->plotter.show_legend) &&\
     (event->x >= pw->plotter.layout.legend_x) &&\
     (event->x <= pw->plotter.layout.legend_x + \
      pw->plotter.layout.legend_width) &&\
     (event->y >= pw->plotter.layout.legend_y) &&\
     (event->y <= pw->plotter.layout.legend_y + \
      pw->plotter.layout.legend_height))

#define DrawDragRect(pw) \
  XDrawRectangle(XtDisplay(pw), XtWindow(pw), pw->plotter.dragGC,\
		 pw->plotter.clickx, pw->plotter.clicky,\
		 pw->plotter.dragwidth, pw->plotter.dragheight)

#define EraseDragRect(pw) DrawDragRect(pw)

#define xscale(pw,x) AtScalePixelToUser(AtAxisGetScale(pw->plotter.xaxis), x)
#define yscale(pw,x) AtScalePixelToUser(AtAxisGetScale(pw->plotter.yaxis), x)
#define y2scale(pw,x) AtScalePixelToUser(AtAxisGetScale(pw->plotter.y2axis), x)


static void UnselectAllInLegend P((AtPlotterWidget pw)); 
static void UnselectAllInLegend(pw)
AtPlotterWidget pw;
{
  HighlightSelectedLegendItem(pw);
  pw->plotter.legend_item = -1;
}

static void SelectInLegend P((AtPlotterWidget pw, 
			      XButtonPressedEvent *event));
static void SelectInLegend(pw, event)
AtPlotterWidget pw;
XButtonPressedEvent *event;
{
  int i;
  short h; 
  AtText *t;

  if (!InLegendRegion(pw,event)) return;

  h = pw->plotter.layout.legend_y
    + AtTextHeight(pw->plotter.legend_title_text)
      + pw->plotter.margin_height
	- pw->plotter.legend_spacing/2;
    
  /* check for click in title -- deselect selected items */
  if (event->y < h) {
    HighlightSelectedLegendItem(pw);  /* erase old one */
    pw->plotter.legend_item = -1;
    return;
  }
    
  for(i=FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++) {
    if (!NTHCHILDISDISPLAYED(pw, i)) continue; 
    t = CONSTRAINT(pw,i)->plotter.legend_text;
    if (t != NULL) {
      if ((event->y >= h) &&
	  (event->y <= h+AtTextHeight(t) + pw->plotter.legend_spacing))
	break;
      h += AtTextHeight(t) + pw->plotter.legend_spacing;
    }
  }

  if (i < NUMCHILDREN(pw)) {
    if ((event->type == ButtonPress) &&
	(i == pw->plotter.legend_item) &&
	(pw->plotter.lastclick != CurrentTime) &&
	(event->time - pw->plotter.lastclick <
	 pw->plotter.double_click_interval)) {   /* if a double click */
      XtCallCallbacks((Widget)pw, XtNdoubleSelectCallback,
		      (caddr_t)CHILD(pw, i));
      pw->plotter.lastclick = CurrentTime;
      pw->plotter.selection_mode = SelectNONE;
    }
    else if ((event->type == ButtonPress) ||
	     (i != pw->plotter.legend_item)) {
      HighlightSelectedLegendItem(pw);  /* erase old one */
      pw->plotter.legend_item = i;
      pw->plotter.legend_item_y = h - pw->plotter.layout.legend_y;
      HighlightSelectedLegendItem(pw);  /* draw new one */
      pw->plotter.lastclick = event->time;
    }
  }
}


static void StartSelection P((AtPlotterWidget pw, 
			      XButtonPressedEvent *event));
static void StartSelection(pw, event)
AtPlotterWidget pw;
XButtonPressedEvent *event;
{
    /*
     * start-selection:
     *   store x,y coords
     *   if in plotting region
     *     selectionMode = plot
     *   if in legend region
     *     selectionMode = legend
     *     unhighlight any previous highlight legend entry
     *     highlight the appropriate legend entry, remember it
     *   else selectionMode = none
     *     unselect any highlight legend entries
     */
    
  pw->plotter.clickx = event->x;
  pw->plotter.clicky = event->y;
    
  if (InPlottingRegion(pw,event)) {
    pw->plotter.selection_mode = SelectREGION;
  }
  else if (InLegendRegion(pw,event)) {
    pw->plotter.selection_mode = SelectLEGEND;
    SelectInLegend(pw,event);
  }
  else {
    pw->plotter.selection_mode = SelectNONE;
    UnselectAllInLegend(pw);
  }
}


static void Drag P((AtPlotterWidget pw, XMotionEvent *event));
static void Drag(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
    /*
     * drag:
     *   if selectionMode = plot
     *     if still in plotting region
     *       if dragging
     *         erase old rectangle
     *       draw new rectangle
     *       dragging = True
     *     else
     *       cancel selection
     *   else if selectionMode = legend
     *     if no longer over the same entry
     *       unhighlight it, forget it
     *       if over a new entry
     *         highlight it, remember it
     */

  if (pw->plotter.selection_mode == SelectREGION) {
    if (InPlottingRegion(pw,event)) {
      if (pw->plotter.dragging) {
	EraseDragRect(pw);
      }
      pw->plotter.dragwidth = event->x - pw->plotter.clickx;
      pw->plotter.dragheight = event->y - pw->plotter.clicky;
      DrawDragRect(pw);
      pw->plotter.dragging = True;
    }
    else {
      if (pw->plotter.dragging) {
	EraseDragRect(pw);
	pw->plotter.dragging = False;
      }
      pw->plotter.selection_mode = SelectNONE;
    }
  }
  else if (pw->plotter.selection_mode == SelectLEGEND)
    SelectInLegend(pw,(XButtonPressedEvent *)event);
}


static void EndSelection P((AtPlotterWidget pw, XMotionEvent *event));
static void EndSelection(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
    /* end-selection:
     *   if selectionMode = plot
     *      if dragging
     *        erase rectangle
     *      if still inside plotting region
     *        if dragging && dragged > 4 pixels
     *          call dragCallback
     *        else
     *          call clickCallback
     *      dragging = False
     *   else if selectionMode = legend
     *      if there is a selected entry
     *         call selectCallback
     *         make it the primary selection
     *         leave the entry highlighted
     *   selectionMode = none
     */

  if (pw->plotter.selection_mode == SelectREGION) {
    if (pw->plotter.dragging) EraseDragRect(pw);
	    
    if (InPlottingRegion(pw,event)) {
      if (pw->plotter.dragging &&
	  (pw->plotter.dragwidth*pw->plotter.dragwidth +
	   pw->plotter.dragheight*pw->plotter.dragheight > 16)) {
	RectangleStruct r;
	bzero(r, sizeof r); 
	if (NTHCHILDISDISPLAYED(pw, CHILD_XAXIS)) {
	  r[0].x = xscale(pw, pw->plotter.clickx);
	  r[1].x = xscale(pw, pw->plotter.clickx+pw->plotter.dragwidth);
	}
	if (NTHCHILDISDISPLAYED(pw, CHILD_YAXIS)) {
	  r[0].y = yscale(pw, pw->plotter.clicky);
	  r[1].y = yscale(pw, pw->plotter.clicky+pw->plotter.dragheight);
	}
	if (NTHCHILDISDISPLAYED(pw, CHILD_Y2AXIS)) {
	  r[0].y2= y2scale(pw, pw->plotter.clicky);
	  r[1].y2=y2scale(pw, pw->plotter.clicky+pw->plotter.dragheight);
	}
	r[0].pixelx = pw->plotter.clickx;
	r[0].pixely = pw->plotter.clicky;
	r[1].pixelx = event->x;
	r[1].pixely = event->y;
	XtCallCallbacks((Widget)pw, XtNdragCallback, (caddr_t)r);
      }
      else {
	PointStruct point;
	bzero(&point, sizeof point);
	point.pixelx = event->x;
	point.pixely = event->y;
	if (NTHCHILDISDISPLAYED(pw, CHILD_XAXIS)) 
	  point.x = xscale(pw, event->x);
	if (NTHCHILDISDISPLAYED(pw, CHILD_YAXIS)) 
	  point.y = yscale(pw, event->y);
	if (NTHCHILDISDISPLAYED(pw, CHILD_Y2AXIS))
	  point.y2 = y2scale(pw, event->y);
	XtCallCallbacks((Widget)pw, XtNclickCallback, (caddr_t)&point);
      }
    }
    pw->plotter.dragging = False;
  }
  else if (pw->plotter.selection_mode == SelectLEGEND) {
    SelectInLegend(pw,(XButtonPressedEvent *)event);
    if ((pw->plotter.legend_item != -1) &&  /* if an item is selected */
	(pw->plotter.lastclick != CurrentTime)) /* and not a dbl-click */
      XtCallCallbacks((Widget)pw, XtNselectCallback,
		      (caddr_t)CHILD(pw,pw->plotter.legend_item));
  }

  pw->plotter.selection_mode = SelectNONE;
}


static void CancelSelection P((AtPlotterWidget pw, XMotionEvent *event));
static void CancelSelection(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
  /*  
   * cancel-selection:
   *   if selectionMode == plot && dragging
   *     erase rectangle
   *   else if selectionMode = legend
   *     unhighlight any highlighted item
   *   selectionMode = none
   */
  
  if ((pw->plotter.selection_mode == SelectREGION) && (pw->plotter.dragging)){
    EraseDragRect(pw);
    pw->plotter.dragging = False;
  }
  else if (pw->plotter.selection_mode == SelectLEGEND)
    UnselectAllInLegend(pw);
  
  pw->plotter.selection_mode = SelectNONE;
}

static void HandleMotion P((AtPlotterWidget pw, XMotionEvent *event));
static void HandleMotion(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
  PointStruct point;
    
  if (InPlottingRegion(pw,event)) {
    bzero(&point, sizeof point); 
    point.pixelx = event->x;
    point.pixely = event->y;
    if (NTHCHILDISDISPLAYED(pw, CHILD_XAXIS)) point.x = xscale(pw,event->x);
    if (NTHCHILDISDISPLAYED(pw, CHILD_YAXIS)) point.y = yscale(pw, event->y);
    if (NTHCHILDISDISPLAYED(pw, CHILD_Y2AXIS)) point.y2 = y2scale(pw,event->y);
    XtCallCallbacks((Widget)pw, XtNmotionCallback, (caddr_t)&point);
  }
}


/*****************************************************************
 *
 * The exported "member" functions
 * 
 */
AtPlotWidget AtPlotterGetSelectedPlot(w)
AtPlotterWidget w;
{
  if (w->plotter.legend_item == -1)
    return NULL;
  else
    return (AtPlotWidget)CHILD(w, w->plotter.legend_item);
}

AtPlotWidget AtPlotterGetLastPlot(w)
AtPlotterWidget w;
{
  if (NUMCHILDREN(w) > FIRST_PUBLIC_CHILD)
    return (AtPlotWidget)CHILD(w, NUMCHILDREN(w) - 1);
  else
    return NULL;
}

AtAxisObject AtPlotterGetXAxis(w)
AtPlotterWidget w;
{
  return w->plotter.xaxis;
}

AtAxisObject AtPlotterGetYAxis(w)
AtPlotterWidget w;
{
  return w->plotter.yaxis;
}

AtAxisObject AtPlotterGetY2Axis(w)
AtPlotterWidget w;
{
  return w->plotter.y2axis;
}

AtAxisObject AtPlotterGetXFrameAxis(w)
AtPlotterWidget w;
{
  return w->plotter.xframeaxis;
}

AtAxisObject AtPlotterGetYFrameAxis(w)
AtPlotterWidget w;
{
  return w->plotter.yframeaxis;
}

AtAxisObject AtPlotterGetY2FrameAxis(w)
AtPlotterWidget w;
{
  return w->plotter.y2frameaxis;
}

void AtPlotterRemoveAllPlots(pw)
AtPlotterWidget pw;
{
  int i;

  for(i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++)
    XtDestroyWidget(CHILD(pw,i));
}


AtPlotWidget AtPlotterCheckHit(pw, x, y)
AtPlotterWidget pw;
int x, y;
{
  int i;
  Boolean (*f) P((AtPlotWidget, int, int));

  for(i = FIRST_PUBLIC_CHILD; i < NUMCHILDREN(pw); i++)
    if (NTHCHILDISDISPLAYED(pw, i))
      if (f = ((AtPlotClassRec *) XtClass(CHILD(pw, i)))->plot_class.checkhit)
	if (f((AtPlotWidget)CHILD(pw, i), x, y)) 
	  return (AtPlotWidget)CHILD(pw,i);
  
  return NULL;
}   


/*
 ******************************************************************
 *
 * RankOrderChildren()
 * Sorts the children of Plotter widget on a list according to their
 * Ranking rather than their birth order.  The lowest ranking child is
 * drawn first, whereas, the highest ranking one is drawn last. The
 * highest ranking plot is therfore always visible (never covered by
 * its siblings, if they overlap).  This is useful, for example, if
 * you have several sets of Barcharts that overlap and you want to
 * control which set should be completely visible (in the foreground)
 * at a given time, and in what order the others should cover each
 * other.
 *
 ********************************************************************
*/

#define ORDLIST parent->plotter.ordered_children


static Rank* getnode P((void)); 
static Rank* getnode ()
{
  return ((Rank*) XtMalloc(sizeof(Rank)));
}

static void RankOrderChildren P((Widget w)); 
static void RankOrderChildren(w)
Widget w;
{
  AtPlotterWidget parent = ParentPlotter(w);
  AtPlotterConstraints pcons = (AtPlotterConstraints) w->core.constraints;
  Rank *locate, *newnode;
  Boolean found = False;

  if (!XtIsSubclass(w, atPlotWidgetClass)) return; /* Don't rank axes */
  
  if (ORDLIST == NULL) {
    ORDLIST = getnode();
    ORDLIST->prev = NULL;
    ORDLIST->next = NULL;
    ORDLIST->child = (AtPlotWidget)w;  
    ORDLIST->rank_order = pcons->plotter.rank_order; 
    /* higher rank children go on top of lower rank ones.
     * rankOrder is a constraint resource of Plotter. */

    return;
  }
  for (locate = ORDLIST; locate != NULL; locate = locate->next)
    if (pcons->plotter.rank_order < locate->rank_order) { 
      /* should be inserted right before locate */
      newnode = getnode(); /* get a new node */ 
      newnode->child = (AtPlotWidget)w;
      newnode->rank_order = pcons->plotter.rank_order;
      newnode->prev = locate->prev;
      newnode->next = locate;
      locate->prev = newnode;
      if (newnode->prev == NULL) /* first on the list */
	ORDLIST = newnode;
      else
	(newnode->prev)->next = newnode;
      found = True;
      break;
    }
  if (!found) { /* Highest order so far, insert at end of list */
    for (locate=ORDLIST; locate->next != NULL; locate=locate->next)
      ;
    newnode = getnode(); /* get a new node */ 
    newnode->child = (AtPlotWidget)w;
    newnode->rank_order = pcons->plotter.rank_order;
	newnode->prev = locate;
    newnode->next = locate->next;
    locate->next = newnode;
  }
}

/*
 ***************************************************************
 * 
 *   ReRankOrderChildren
 *   Remove the child whose ranking changed form the 
 *   ordered_children list. Then it will reinsert the removed
 *   child into the list according to its new rankOrder.
 *
 ***************************************************************
 */

static void ReRankOrderChildren P((Widget w)); 
static void ReRankOrderChildren(w)
Widget w;
{
  RankOrderRemove(w); 
  RankOrderChildren(w); 
}
 
static void RankOrderRemove P((Widget w)); 
static void RankOrderRemove(w)
Widget w;
{
  AtPlotterWidget parent = ParentPlotter(w);
  Rank *locate;

  if (!XtIsSubclass(w, atPlotWidgetClass)) return; /* Don't rank axes */
  
  for (locate = ORDLIST; locate != NULL; locate = locate->next)  {
    if (locate->child == (AtPlotWidget)w) { 
      if (locate->next)
	(locate->next)->prev = locate->prev;
      if (locate->prev == NULL) /* head of list */
	ORDLIST = locate->next;
      else
	(locate->prev)->next = locate->next;
      XtFree ((char *)locate);
      break;
    }
  }
}

#undef ORDLIST
/*
 * Need to use eval rather than just c-style as hack-local-variables
 * is called after the c-mode-hook. 
 *
 * Local Variables:
 * eval: (set-c-style 'GNU)
 * End:
 */
