/*
 *      Plotter.c
 *
 *      The AthenaTools Plotter Widget Set - Version 6.0
 *
 *      klin, Tue Jul  7 13:59:47 1992
 *      klin, Sun Jul 19 19:23:41 1992, patchlevel 1
 *                                      AtPlotterGetLegendWidth() added
 *      klin, Mon Jul 27 14:17:20 1992, patchlevel 2
 *                                      Resources XtNlegendLeft and
 *                                      XtNautoRedisplay added.
 *                                      Resource XtNusePixmap and
 *                                      drawing to a pixmap added.
 *                                      Resource XtNuseCursor and
 *                                      callback cursors added.
 *                                      Resource XtNbusyCallback and
 *                                      busy callback added.
 *      klin, Sun Aug  2 18:24:39 1992, patchlevel 3
 *                                      Layout callback and some stuff for
 *                                      aligning axis positions added.
 *                                      Resource XtNtitleHeigth and
 *                                      AtPlotterGetTitleHeigth() added.
 *                                      Resources XtNxxxCursor added.
 *                                      Callbacks for entering and leaving
 *                                      the plotter window added.
 *                                      Method query_geometry added.
 *      klin, Fri Aug  7 10:33:36 1992, Minor changes to keep
 *                                      ANSI C compilers quiet.
 *      klin, Sat Aug 15 10:08:25 1992, patchlevel 4
 *                                      Resources XtNslideCallback and
 *                                      XtNslideCursor and needed stuff added.
 *                                      Resources XtNselectCallback and
 *                                      XtNselectCursor and needed stuff added.
 *                                      Minor changes in callbacks.
 *                                      Changed <At/..> to <X11/At/..>.
 */
static char SCCSid[] = "@(#) Plotter V6.0  92/08/15  Plotter.c";

/*

Copyright 1992 by University of Paderborn
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 firms, institutes
or employers of the authors not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

THE AUTHORS AND THEIR FIRMS, INSTITUTES OR EMPLOYERS DISCLAIM ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE AUTHORS AND THEIR FIRMS,
INSTITUTES OR EMPLOYERS 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.

*/

#include <X11/At/AtConverters.h>
#include <X11/At/Scale.h>
#include <X11/At/PlotterP.h>
#include <X11/At/Plot.h>
#include <X11/At/AxisCoreP.h>

#define ICON_WIDTH        16  /* Width of icons */
#define PIXMAP_ALIGNMENT 128  /* Alignment of pixmap width/height */
#define DEFAULT_WIDTH    256  /* Default plotter width */
#define DEFAULT_HEIGHT   128  /* Default plotter height */
#define NO_ITEM          -1   /* No item selected */

#define NUMCHILDREN(w) (w->composite.num_children)
#define CHILD(w,i) ((AtPlotWidget)(w->composite.children[i]))
#define CONSTRAINT(w,i) \
     ((AtPlotterConstraints)(((Widget) 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 USESX2AXIS(cw)   (CONSTRAINTS(cw)->plotter.use_x2_axis)
#define USESY2AXIS(cw)   (CONSTRAINTS(cw)->plotter.use_y2_axis)

#define HasLayoutCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNlayoutCallback) == XtCallbackHasSome)
#define HasBusyCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNbusyCallback) == XtCallbackHasSome)
#define HasClickCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNclickCallback) == XtCallbackHasSome)
#define HasMotionCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNmotionCallback) == XtCallbackHasSome)
#define HasDragCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNdragCallback) == XtCallbackHasSome)
#define HasSlideCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNslideCallback) == XtCallbackHasSome)
#define HasSelectCB(pw) \
     (XtHasCallbacks((Widget) pw, XtNselectCallback) == XtCallbackHasSome)

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

#define AtNewString(str) str = XtNewString(str)

/*
 *   The resources
 */

#define off(field) XtOffset(AtPlotterWidget, plotter.field)
static XtResource plotter_resources[] = {
  {
     XtNfontFamily, XtCFontFamily,
     XtRString, sizeof(String),
     off(font_family), XtRString, (XtPointer) DFLT_FONTFAMILY
  },
  {
     XtNtitle, XtCTitle,
     XtRString, sizeof(String),
     off(title), XtRString, (XtPointer) NULL
  },
  {
     XtNlegendTitle, XtCLegendTitle,
     XtRString, sizeof(String),
     off(legend_title), XtRString, (XtPointer) "Legend"
  },
  {
     XtNtitleSize, XtCFontSize,
     XtRFontSize, sizeof(int),
     off(title_size), XtRImmediate, (XtPointer) DFLT_FONTBIG
  },
  {
     XtNtitleStyle, XtCFontStyle,
     XtRFontStyle, sizeof(int),
     off(title_style), XtRImmediate, (XtPointer) AtFontPLAIN
  },
  {
     XtNlegendTitleSize, XtCFontSize,
     XtRFontSize, sizeof(int),
     off(legend_title_size), XtRImmediate, (XtPointer) DFLT_FONTNORMAL
  },
  {
     XtNlegendTitleStyle, XtCFontStyle,
     XtRFontStyle, sizeof(int),
     off(legend_title_style), XtRImmediate, (XtPointer) AtFontPLAIN
  },
  {
     XtNlegendSize, XtCFontSize,
     XtRFontSize, sizeof(int),
     off(legend_size), XtRImmediate, (XtPointer) DFLT_FONTNORMAL
  },
  {
     XtNlegendStyle, XtCFontStyle,
     XtRFontStyle, sizeof(int),
     off(legend_style), XtRImmediate, (XtPointer) AtFontPLAIN
  },
  {
     XtNtitleColor, XtCForeground,
     XtRPixel, sizeof(Pixel),
     off(title_color), XtRString, (XtPointer) XtDefaultForeground
  },
  {
     XtNlegendColor, XtCForeground,
     XtRPixel, sizeof(Pixel),
     off(legend_color), XtRString, (XtPointer) XtDefaultForeground
  },
  {
     XtNplotAreaColor, XtCForeground,
     XtRPixel, sizeof(Pixel),
     off(plotarea_color), XtRString, (XtPointer) XtDefaultBackground
  },
  {
     XtNshowTitle, XtCShowTitle,
     XtRBoolean, sizeof(Boolean),
     off(show_title), XtRImmediate, (XtPointer) True
  },
  {
     XtNshowLegend, XtCShowLegend,
     XtRBoolean, sizeof(Boolean),
     off(show_legend), XtRImmediate, (XtPointer) True
  },
  {
     XtNtitleHeight, XtCTitleHeight,
     XtRDimension, sizeof(Dimension),
     off(default_title_height), XtRImmediate, (XtPointer) 0
  },
  {
     XtNlegendLeft, XtCLegendLeft,
     XtRBoolean, sizeof(Boolean),
     off(legend_left), XtRImmediate, (XtPointer) False
  },
  {
     XtNlegendWidth, XtCLegendWidth,
     XtRDimension, sizeof(Dimension),
     off(default_legend_width), XtRImmediate, (XtPointer) 0
  },
  {
     XtNlegendSpacing, XtCMargin,
     XtRDimension, sizeof(Dimension),
     off(legend_spacing), XtRImmediate, (XtPointer) 3
  },
  {
     XtNmarginWidth, XtCMargin,
     XtRDimension, sizeof(Dimension),
     off(margin_width), XtRImmediate, (XtPointer) 3
  },
  {
     XtNmarginHeight, XtCMargin,
     XtRDimension, sizeof(Dimension),
     off(margin_height), XtRImmediate, (XtPointer) 3
  },
  {
     XtNrankChildren, XtCRankChildren,
     XtRBoolean, sizeof(Boolean),
     off(rank_children), XtRImmediate, (XtPointer) False
  },
  {
     XtNautoRedisplay, XtCAutoRedisplay,
     XtRBoolean, sizeof(Boolean),
     off(auto_redisplay), XtRImmediate, (XtPointer) True
  },
  {
     XtNuseCursors, XtCUseCursors,
     XtRBoolean, sizeof(Boolean),
     off(use_cursors), XtRImmediate, (XtPointer) True
  },
  {
     XtNusePixmap, XtCUsePixmap,
     XtRBoolean, sizeof(Boolean),
     off(use_pixmap), XtRImmediate, (XtPointer) False
  },
  /* The plotter cursors */
  {
     XtNplotterCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(plotter_cursor), XtRString, (XtPointer) None
  },
  {
     XtNbusyCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(busy_cursor), XtRString, (XtPointer) "watch"
  },
  {
     XtNmotionCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(motion_cursor), XtRString, (XtPointer) "crosshair"
  },
  {
     XtNclickCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(click_cursor), XtRString, (XtPointer) "crosshair"
  },
  {
     XtNdragCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(drag_cursor), XtRString, (XtPointer) "crosshair"
  },
  {
     XtNslideCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(slide_cursor), XtRString, (XtPointer) "fleur"
  },
  {
     XtNselectCursor, XtCCursor,
     XtRCursor, sizeof(Cursor),
     off(select_cursor), XtRString, (XtPointer) "hand1"
  },
  /* The plotter callbacks */
  {
     XtNlayoutCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(layout_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNbusyCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(busy_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNmotionCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(motion_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNclickCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(click_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNdragCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(drag_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNslideCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(slide_callback), XtRCallback, (XtPointer) NULL
  },
  {
     XtNselectCallback, XtCCallback,
     XtRCallback, sizeof(XtCallbackList),
     off(select_callback), XtRCallback, (XtPointer) NULL
  },
  /* The axes */
  {
     XtNxAxis, XtCXAxis,
     XtRWidget, sizeof(Widget),
     off(xaxis), XtRImmediate, (XtPointer) NULL
  },
  {
     XtNyAxis, XtCYAxis,
     XtRWidget, sizeof(Widget),
     off(yaxis), XtRImmediate, (XtPointer) NULL
  },
  {
     XtNx2Axis, XtCX2Axis,
     XtRWidget, sizeof(Widget),
     off(x2axis), XtRImmediate, (XtPointer) NULL
  },
  {
     XtNy2Axis, XtCY2Axis,
     XtRWidget, sizeof(Widget),
     off(y2axis), XtRImmediate, (XtPointer) NULL
  }
};
#undef off

/*
 *   The default bounding box is a nonsensical one, with xmin > xmax.
 *   Unless it is modified to a sensible value, the plotter will ignore it.
 *   This is so that TextPlots (annotations), axes and similar plot types
 *   won't be involved in the axis scale computations.
 */

static BoundingBox default_bounding_box = {1.0, 0.0, 0.0, 0.0};

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

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 XtGeometryResult QueryGeometry P((AtPlotterWidget, XtWidgetGeometry *,
					 XtWidgetGeometry *));

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 Boolean Layout P((AtPlotterWidget));
static void Redraw P((AtPlotterWidget, Window, Drawable, Region));
static Boolean RecalcLegend P((AtPlotterWidget));
static void RedrawLegend P((AtPlotterWidget, Region, Boolean));

static void ReRankOrderChildren P((Widget));
static void RankOrderChildren P((Widget));
static void RankOrderRemove P((Widget));

static void InstallHandlers P((AtPlotterWidget));
static void RemoveHandlers P((AtPlotterWidget));
static void EnterLeave P((AtPlotterWidget, XtPointer, XEvent *, int));

static void HandleMotion P((AtPlotterWidget, XMotionEvent *));
static void StartSelection P((AtPlotterWidget, XButtonPressedEvent *));
static void Drag P((AtPlotterWidget, XMotionEvent *));
static void EndSelection P((AtPlotterWidget, XButtonReleasedEvent *));
static void StartSliding P((AtPlotterWidget, XButtonPressedEvent *));
static void Slide P((AtPlotterWidget, XMotionEvent *));
static void EndSliding P((AtPlotterWidget, XButtonReleasedEvent *));
static void Cancel P((AtPlotterWidget, XMotionEvent *));

static void SetDragPositions P((AtPlotterWidget));
static void GetAxisPositions P((AtPlotterWidget, AtAxisPositions *));
static Boolean SetAxisPositions P((AtPlotterWidget, AtAxisPositions *));

/*
 *   The default translations
 */

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

/*
 *   The actions
 */

static XtActionsRec actions[] =
{
     { "motion-notify",       (XtActionProc) HandleMotion },
     { "start-selection",     (XtActionProc) StartSelection },
     { "drag",                (XtActionProc) Drag },
     { "end-selection",       (XtActionProc) EndSelection},
     { "start-sliding",       (XtActionProc) StartSliding },
     { "slide",               (XtActionProc) Slide },
     { "end-sliding",         (XtActionProc) EndSliding },
     { "cancel",              (XtActionProc) Cancel }
};

#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      */  TRUE,
    /* compress_exposure    */  XtExposeCompressMultiple |
				XtExposeGraphicsExposeMerged,
    /* compress_enterleave  */  FALSE,
    /* 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       */  (XtGeometryHandler) QueryGeometry,
    /* 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;


/*
 *   Class initialize
 */

static void ClassInitialize()
{
     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;
     AtRegisterDoubleConverter();
     AtRegisterFontSizeConverter();
     AtRegisterFontStyleConverter();
     *SCCSid = *SCCSid;       /* Keep gcc quiet */
}

/*
 *   Helper functions
 */

static void GetTitle P((AtPlotterWidget));
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, w->plotter.title_style);
     else
	  w->plotter.title_text = NULL;
}

static void FreeTitle P((AtPlotterWidget));
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));
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,
			    w->plotter.legend_title_style);
     else
	  w->plotter.legend_title_text = NULL;
}

static void FreeLegendTitle P((AtPlotterWidget));
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, AtPlotterWidget));
static void GetLegendText(c, p)
AtPlotterConstraints c;
AtPlotterWidget p;
{
     if (c->plotter.legend_name != NULL) {
	  c->plotter.legend_name =
	       AtNewString(c->plotter.legend_name);
	  c->plotter.legend_text =
	       AtTextCreate(c->plotter.legend_name, p->plotter.ff,
			    p->plotter.legend_size, p->plotter.legend_style);
     }
     else
	  c->plotter.legend_text = NULL;
}

static void FreeLegendText P((AtPlotterConstraints, AtPlotterConstraints));
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;
     }
     cur->plotter.legend_name = NULL;
     cur->plotter.legend_text = NULL;
}

/*
 *   Helper routines for pixmaps
 */

/* Align pixmap width and height */
static int AlignPixmap P((int));
static int AlignPixmap(n)
int n;
{
     int m;

     if (n <= PIXMAP_ALIGNMENT)
	  n = PIXMAP_ALIGNMENT;
     else if(m = (n % PIXMAP_ALIGNMENT))
	  n += PIXMAP_ALIGNMENT - m;
     return n;
}

/* Get pixmap gc */
static void GetPixmapGC P((AtPlotterWidget));
static void GetPixmapGC(pw)
AtPlotterWidget pw;
{
     XGCValues gcv;

     if (pw->plotter.pixmap_gc)
	  XtReleaseGC((Widget) pw, pw->plotter.pixmap_gc);
     gcv.foreground = pw->core.background_pixel;
     pw->plotter.pixmap_gc = XtGetGC((Widget) pw, GCForeground, &gcv);
}

/* Free pixmap */
static void FreePixmap P((AtPlotterWidget));
static void FreePixmap(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;

     if (pp->pixmap)
	  XFreePixmap(XtDisplay(pw), pp->pixmap);
     if (pp->pixmap_gc)
	  XtReleaseGC((Widget) pw, pp->pixmap_gc);
     pp->pixmap = None;
     pp->pixmap_gc = None;
     pp->pixmap_width = pp->pixmap_height = 0;
     pp->pixmap_required = False;
}

/* Get pixmap */
static void GetPixmap P((AtPlotterWidget));
static void GetPixmap(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     unsigned int w, h, d;

     w = AlignPixmap(pw->core.width);
     h = AlignPixmap(pw->core.height);
     if ( !pp->pixmap || (w != pp->pixmap_width || h != pp->pixmap_height)) {
	  d = DefaultDepthOfScreen(XtScreen(pw));
	  if (pp->pixmap)
	       XFreePixmap(XtDisplay(pw), pp->pixmap);
	  pp->pixmap = XCreatePixmap(XtDisplay(pw), XtWindow(pw), w, h, d);
	  if ( !pp->pixmap_gc)
	       GetPixmapGC(pw);
	  pp->pixmap_width = w;
	  pp->pixmap_height = h;
     }
     XFillRectangle(XtDisplay(pw), pp->pixmap, pp->pixmap_gc, 0, 0, w, h);
     pp->pixmap_required = False;
}

/*
 *   Initialize
 */

static void Initialize(request, new)
AtPlotterWidget request, new;
{
     XGCValues gcv;
     AtPlotterPart *pp = &new->plotter;
     Boolean mirror;

     /* Make private copies of string resource */
     pp->font_family = AtNewString(pp->font_family);
     pp->title = AtNewString(pp->title);
     pp->legend_title = AtNewString(pp->legend_title);

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

     gcv.foreground = pp->title_color;
     pp->title_gc = XtGetGC((Widget) new, GCForeground, &gcv);

     gcv.foreground = pp->legend_color;
     pp->legend_gc = XtGetGC((Widget) new, GCForeground, &gcv);

     gcv.foreground = new->core.background_pixel;
     gcv.background = pp->legend_color;
     pp->select_gc = XtGetGC((Widget) new, GCForeground | GCBackground, &gcv);

     gcv.foreground = pp->plotarea_color;
     pp->plotarea_gc = XtGetGC((Widget) new, GCForeground, &gcv);

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

     pp->ordered_children = NULL;

     /* Set the pixmap states */
     pp->pixmap = None;
     pp->pixmap_gc = None;
     pp->pixmap_required = pp->use_pixmap;

     /* Create the drag and slide cursors */
     pp->top_left_cursor      = XCreateFontCursor(XtDisplay(new), XC_ul_angle);
     pp->top_right_cursor     = XCreateFontCursor(XtDisplay(new), XC_ur_angle);
     pp->bottom_left_cursor   = XCreateFontCursor(XtDisplay(new), XC_ll_angle);
     pp->bottom_right_cursor  = XCreateFontCursor(XtDisplay(new), XC_lr_angle);

     /* Set the current cursor */
     if (pp->use_cursors && pp->plotter_cursor) {
	  pp->current_cursor = pp->plotter_cursor;
     }
     else {
	  pp->current_cursor = None;
     }

     /* Set these state flags */
     pp->rescale_required = pp->layout_required = pp->redraw_required = True;
     pp->expose_requested = True;  /* Redraw will come from map */

     /* Reset the other state flags and data */
     pp->redisplay_required = False;
     pp->in_layout_mode = False;
     pp->position_required = False;
     pp->positions.position = AtPositionNONE;
     pp->in_select = pp->in_click = pp->in_select = FALSE;
     pp->in_drag = pp->in_slide = pp->can_slide = False;
     pp->selected_item = NO_ITEM;

     if (pp->xaxis) {
	  XtCheckSubclass((Widget) pp->xaxis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as xAxis");
	  XtVaGetValues((Widget) pp->xaxis, XtNmirror,
			(XtPointer) &mirror, NULL);
	  if (mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "XAxis widget should have XtNmirror false");
	       XtVaSetValues((Widget) pp->xaxis, XtNmirror, False, NULL);
	  }
     }
     if (pp->yaxis) {
	  XtCheckSubclass((Widget) pp->yaxis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as yAxis");
	  XtVaGetValues((Widget) pp->yaxis, XtNmirror,
			(XtPointer) &mirror, NULL);
	  if (mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "YAxis widget should have XtNmirror false");
	       XtVaSetValues((Widget) pp->yaxis, XtNmirror, False, NULL);
	  }
     }
     if (pp->x2axis) {
	  XtCheckSubclass((Widget) pp->x2axis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as x2Axis");
	  XtVaGetValues((Widget) pp->x2axis, XtNmirror,
			(XtPointer) &mirror, NULL);
	  if (!mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "X2Axis widget should have XtNmirror True");
	       XtVaSetValues((Widget) pp->x2axis, XtNmirror, True, NULL);
	  }
     }
     if (pp->y2axis) {
	  XtCheckSubclass((Widget) pp->y2axis, atAxisCoreWidgetClass,
			  "AtPlotter needs subclass of AtAxisCore as y2Axis");
	  XtVaGetValues((Widget) pp->y2axis, XtNmirror,
			(XtPointer) &mirror, NULL);
	  if (!mirror) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "y2Axis widget should have XtNmirror True");
	       XtVaSetValues((Widget) pp->y2axis, XtNmirror, True, NULL);
	  }
     }
}

/*
 *   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));
static void RequestSyntheticExpose(pw)
AtPlotterWidget pw;
{
     XExposeEvent ev;

     if ( !pw->plotter.expose_requested && XtWindow(pw)) {
#ifdef TRACE
	  fprintf(stderr, "<<<<Synth expose sent\n");
#endif
	  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 = True;
     }
}


/*
 *   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));
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, int, int));
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

/*
 *   Calculate the new raw bounding box for the whole plot.
 *   Return TRUE if  it changed, else FALSE.
 *   This is modified by the axes to make the actual bounding box.
 */

static Boolean NewRawBoundingBox P((AtPlotterWidget));
static Boolean NewRawBoundingBox(pw)
AtPlotterWidget pw;
{
     AtPlotterBoundingBox nbb;
     BoundingBox *cbbp;
     int i;
     Boolean ret;

     nbb.xmin = nbb.ymin = nbb.x2min = nbb.y2min = HUGE_VAL;
     nbb.xmax = nbb.ymax = nbb.x2max = nbb.y2max = -HUGE_VAL;

     for (i = 0; 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 */
	       if (CONSTRAINT(pw, i)->plotter.use_x2_axis) {
		    nbb.x2max = Max(nbb.x2max, cbbp->xmax);
		    nbb.x2min = Min(nbb.x2min, cbbp->xmin);
	       } else {
		    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.raw_bounding_box.fld)
     ret = dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax) ||
	  dif(x2min) || dif(x2max) || dif(y2min) || dif(y2max);
#undef dif
#ifdef TRACE
     fprintf(stderr,
	     "NewRawBoundingBox %s: %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f \n",
	     ret ? "Changed" : "Kept",
	     nbb.xmin, nbb.xmax, nbb.x2min, nbb.x2max,
	     nbb.ymin, nbb.ymax, nbb.y2min, nbb.y2max);
#endif
     pw->plotter.raw_bounding_box = nbb;
     return ret;
}

/*
 *   Merge the second boundingbox into the first, return True if the bb changed.
 *   (These are per-plot BB,s in the constraint record).
 *   This is used by the data extended code.
 *
 */

static Boolean MergeBoundingBox P((BoundingBox *, BoundingBox *));
static Boolean MergeBoundingBox(ob, nb)
BoundingBox *ob, *nb;
{
     BoundingBox old;
     Boolean ret;

     old = *ob;
     ob->xmax = Max(ob->xmax, nb->xmax);
     ob->xmin = Min(ob->xmin, nb->xmin);
     ob->ymax = Max(ob->ymax, nb->ymax);
     ob->ymin = Min(ob->ymin, nb->ymin);
#define dif(fld) (old.fld != ob->fld)
     ret = dif(xmin) || dif(xmax) || dif(ymin) || dif(ymax);
#undef dif

#ifdef TRACE
     if (ret) {
	  fprintf(stderr, "MergeBoundingBox changed to %.1f,%.1f %.1f,%.1f\n",
		  ob->xmin, ob->xmax, ob->ymin, ob->ymax);
     } else {
	  fprintf(stderr, "MergeBoundingBox kept %.1f,%.1f %.1f,%.1f\n",
		  ob->xmin, ob->xmax, ob->ymin, ob->ymax);
     }
#endif
     return ret;
}

/*
 *   These routines are called by children as well as the parent to
 *   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);

     if (ISDISPLAYED(cw)) {
	  if (bb_changed && NewRawBoundingBox(pw)) {
	       /* Overall bb has changed, so request an overall rescale */
	       pw->plotter.rescale_required = True;
	  }
	  /* 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;

     if (ISDISPLAYED(cw)) {
	  if (NewRawBoundingBox(pw)) {
	       /* Overall bb has changed, so request an overall rescale */
	       pw->plotter.rescale_required = True;
	  }
	  /*
	   * 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))
	  XtAppError(XtWidgetToApplicationContext((Widget) cw),
		     "AtPlotterRefreshRequired requires an AtPlot widget");

     if (ISDISPLAYED(cw))  {
	  CONSTRAINTS(cw)->plotter.needs_refresh = True;
	  RequestSyntheticExpose(ParentPlotter(cw));
     }
}

void AtPlotterRedrawRequired(cw)
AtPlotWidget cw;
{
     if (!XtIsSubclass((Widget) cw, atPlotWidgetClass))
	  XtAppError(XtWidgetToApplicationContext((Widget) cw),
		     "AtPlotterRedrawRequired requires an AtPlot widget");

     if (ISDISPLAYED(cw)) {
	  ParentPlotter(cw)->plotter.redraw_required = True;
	  RequestSyntheticExpose(ParentPlotter(cw));
     }
}

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

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

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

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

/* Request a complete recalc of just this plot - used by axis code */
void AtPlotterRecalcThisPlot(cw)
AtPlotWidget cw;
{
     AtPlotterWidget pw = ParentPlotter(cw);

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

     if (ISDISPLAYED(cw)) {
	  if (pw->plotter.in_layout_mode) {
	       /* We have to use this one to avoid infinite loops */
	       pw->plotter.rescale_required = True;
	  } else {
	       AddExtendedList(cw, 0, -1);
	       RequestSyntheticExpose(pw);
	  }
     }
}

/*
 *   Get legend width and title height
 *
 *   These routines may be called from other plotters or applications
 *   to get the maximal legend width and title height from the plotter.
 */

int AtPlotterGetLegendWidth(pw)
AtPlotterWidget pw;
{
     AtPlotterConstraints c;
     AtPlotterPart *pp = &pw->plotter;
     int w = 0, i;

     if ( !XtIsRealized((Widget) pw))
	  w = -1;
     else {
	  for(i = 0; i < NUMCHILDREN(pw); i++) {
	       c = CONSTRAINT(pw, i);
	       if (c->plotter.legend_text) {
		    w = Max(w, AtTextWidth(c->plotter.legend_text));
	       }
	  }
	  w += ICON_WIDTH + 2 * pp->margin_width;
	  w = Max(w, AtTextWidth(pp->legend_title_text));
     }
     return w;
}

int AtPlotterGetTitleHeight(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     int h = 0;

     if ( !XtIsRealized((Widget) pw))
	  h = -1;
     else if (pp->title_text)
	  h = AtTextHeight(pp->title_text);
     return h;
}

/*
 *   Get and set plotter's axis positions
 *
 *   These routines may be called from other plotters or applications
 *   to get the current axis positions and to set required axis positions,
 *   i.e. to align axis positions of multiple plotters.
 */

Boolean AtPlotterGetAxisPositions(pw, ap)
AtPlotterWidget pw;
AtAxisPositions *ap;
{
     if ( !XtIsRealized((Widget) pw) || ap == NULL)
	  return False;

     /*
      * Recalc the plotter layout if needed and get
      * the current axis positions.
      */
     if (pw->plotter.layout_required) {
	  (void) Layout(pw);
	  pw->plotter.layout_required = False;
     }
     GetAxisPositions(pw, ap);
     return True;
}

void AtPlotterSetAxisPositions(pw, ap)
AtPlotterWidget pw;
AtAxisPositions *ap;
{
     if ( !XtIsRealized((Widget) pw) || ap == NULL)
	  return;

     /*
      * If there is any axis position alignment required
      * recalc the layout if needed. Then set the required
      * axis positions and force redisplay if there were
      * any changes detected.
      */
     if (ap->position && !pw->plotter.position_required) {
	  if (pw->plotter.layout_required) {
	       (void) Layout(pw);
	       pw->plotter.layout_required = False;
	  }
	  if (SetAxisPositions(pw, ap)) {
	       pw->plotter.position_required = True;
	       RequestSyntheticExpose(pw);
	  }
     }
}

/*
 *   Get and set plotter's selected plot widget
 */

Widget AtPlotterGetSelectedPlot(pw)
AtPlotterWidget pw;
{
     int i;

     if (XtIsRealized((Widget) pw) && pw->plotter.selected_item != NO_ITEM) {
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       if (i == pw->plotter.selected_item)
		    return (Widget) CHILD(pw, i);
	  }
     }
     return NULL;
}

Boolean AtPlotterSetSelectedPlot(pw, w)
AtPlotterWidget pw;
Widget w;
{
     int i;

     if (XtIsRealized((Widget) pw)) {
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       if (w = (Widget) CHILD(pw, i)) {
		    pw->plotter.selected_item = i;
		    if (pw->plotter.show_legend)
			 RequestSyntheticExpose(pw);
		    return True;
	       }
	  }
     }
     return False;
}

/*
 *   These private ones are called from this file
 *   and are passed the parent widget
 */

static void RescaleRequired P((AtPlotterWidget));
static void RescaleRequired(pw)
AtPlotterWidget pw;
{
     pw->plotter.rescale_required = True;
     RequestSyntheticExpose(pw);
}

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

static void RedrawRequired P((AtPlotterWidget));
static void RedrawRequired(pw)
AtPlotterWidget pw;
{
     if (pw->plotter.redisplay_required)
	  pw->plotter.expose_requested = False;
     pw->plotter.redraw_required = True;
     RequestSyntheticExpose(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 four axes changed.
 *
 *   XXX - assume the axes know enough to answer AxisWidth sensibly!
 *   XXX - also assumes axis.max & axis.min are determined!
 */

static Boolean Layout(pw)
AtPlotterWidget pw;
{
     AtPlotterLayout *lp = &pw->plotter.layout;
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterLayout old;
     int xwid, ywid, x2wid, y2wid;
     Boolean changed;

     /* Layout is set from axis positions! No recalc needed */
     if (pp->positions.position)
	  return False;

     old = pp->layout;

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

     /* The legend at RHS or LHS of the plotting area */
     if (pp->show_legend) {
	  if (pp->legend_left) {
	       lp->legend_x = lp->x1 + pp->margin_width;
	       lp->x1 += lp->legend_width + pp->margin_width;
	  }
	  else {
	       lp->x2 -= lp->legend_width + pp->margin_width;
	       lp->legend_x = lp->x2 + pp->margin_width;
	  }
     }

     /* Assume title at top at present */
     if (pp->show_title && pp->title_text) {
	  lp->title_y = lp->y1 + AtTextAscent(pp->title_text);
	  if (pp->default_title_height > 0)
	       lp->y1 += pp->default_title_height + pp->margin_height;
	  else
	       lp->y1 += AtTextHeight(pp->title_text) + pp->margin_height;
     }

     /* Calculate the "width" of the axes */
     xwid = ywid = x2wid = y2wid = 0;
     if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	  xwid = AtAxisWidth(pp->xaxis);
     if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	  ywid = AtAxisWidth(pp->yaxis);
     if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	  x2wid = AtAxisWidth(pp->x2axis);
     if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	  y2wid = AtAxisWidth(pp->y2axis);

     lp->y1 += x2wid;
     lp->y2 -= xwid;
     lp->x1 += ywid;
     lp->x2 -= y2wid;

     lp->width  = lp->x2 - lp->x1 + 1;
     lp->height = lp->y2 - lp->y1 + 1;

     /* Don't infinite-loop in braindead small windows! */
     if (lp->width < 1) {
	  lp->width = 1;
	  lp->x2 = lp->x1 + 1;
     }
     if (lp->height < 1) {
	  lp->height = 1;
	  lp->y2 = lp->y1 + 1;
     }

     if (pp->show_title && pp->title_text) {
	  lp->title_x = lp->x1 +
	       (lp->width - AtTextWidth(pp->title_text)) / 2;
     }

     if (pp->show_legend) {
	  lp->legend_y = lp->y1 + (lp->height - lp->legend_height)/2;
     }

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

#ifdef TRACE
     fprintf(stderr, "In Layout, %s %d,%d to %d,%d\n",
	     changed ? "chose" : "kept",
	     lp->x1, lp->y1, lp->x2, lp->y2);
#endif
     return changed;
}

/*
 *   Get the current axis positions from the plotter layout.
 */

static void GetAxisPositions(pw, ap)
AtPlotterWidget pw;
AtAxisPositions *ap;
{
     AtPlotterLayout *lp = &pw->plotter.layout;

     /* Calc the relative positions of the axes from layout */
     ap->yaxis  = pw->core.x + lp->x1;
     ap->y2axis = pw->core.x + lp->x2;
     ap->xaxis  = pw->core.y + lp->y2;
     ap->x2axis = pw->core.y + lp->y1;
     ap->position = AtPositionNONE;
}

/*
 *   Recalc the plotter layout from possible external given axis positions.
 *   Dependent on required axes as defined in the position mask
 *   set the axis positions and recalc legend width and/or title height.
 *   Return True if any axis position has changed, False otherwise.
 */

static Boolean SetAxisPositions(pw, np)
AtPlotterWidget pw;
AtAxisPositions *np;
{
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterLayout *lp = &pw->plotter.layout;
     AtAxisPositions *ap = &pw->plotter.positions;
     Dimension w;
     Boolean changed = False;

     /* Check for changes */
     ap->position = AtPositionNONE;
#define dif(v) (np->v != ap->v)
     if (np->position & AtPositionXAXES) {
	  if(dif(xaxis) || dif(x2axis)) {
	       changed = True;
	       ap->position |= AtPositionXAXES;
	       ap->xaxis  = np->xaxis;
	       ap->x2axis = np->x2axis;
	  }
     }
     if (np->position & AtPositionYAXES) {
	  if(dif(yaxis) || dif(y2axis)) {
	       changed = True;
	       ap->position |= AtPositionYAXES;
	       ap->yaxis  = np->yaxis;
	       ap->y2axis = np->y2axis;
	  }
     }
#undef dif

     if (changed) {
	  /* Layout from X axis positions */
	  if (ap->position & AtPositionXAXES) {
	       lp->y1 = ap->x2axis - pw->core.y;
	       lp->y2 = ap->xaxis  - pw->core.y;
	       lp->height = lp->y2 - lp->y1 + 1;
	  }
	  /* Layout from Y axis positions */
	  if (ap->position & AtPositionYAXES) {
	       lp->x1 = ap->yaxis  - pw->core.x;
	       lp->x2 = ap->y2axis - pw->core.x;
	       lp->width  = lp->x2 - lp->x1 + 1;
	  }
	  /* Legend */
	  if (pp->show_legend) {
	       w = 0;
	       if (pp->legend_left) {
		    if (pp->yaxis && ISDISPLAYED(pp->yaxis))
			 w = AtAxisWidth(pp->yaxis);
		    lp->legend_x = lp->x1 - w - lp->legend_width
				   - pp->margin_width;
	       }
	       else {
		    if (pp->y2axis && ISDISPLAYED(pp->y2axis))
			 w = AtAxisWidth(pp->y2axis);
		    lp->legend_x = lp->x2 + w + pp->margin_width;
	       }
	       lp->legend_y = lp->y1 + (lp->height - lp->legend_height)/2;
	  }
	  /* Title */
	  if (pp->show_title && pp->title_text) {
	       lp->title_y = pp->margin_height + AtTextAscent(pp->title_text);
	       lp->title_x = lp->x1 + (lp->width
			     - AtTextWidth(pp->title_text)) / 2;
	  }
     }

     return changed;
}

/*
 *   Ask the axes to actually decide on endpoints.
 *   Returns True if any endpoints changed.
 */

static Boolean DecideAxisValues P((AtPlotterWidget));
static Boolean DecideAxisValues(pw)
AtPlotterWidget pw;
{
     AtPlotterBoundingBox *bbp = &pw->plotter.bounding_box;
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterBoundingBox obb;
     double dummy_min, dummy_max;
     Boolean changed;

#ifdef TRACE
     fprintf(stderr,
	     "Axis Values: WAS %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f\n",
	     bbp->xmin, bbp->xmax, bbp->ymin, bbp->ymax,
	     bbp->x2min, bbp->x2max, bbp->y2min, bbp->y2max);
#endif
     /* First, make a copy of the raw bounding box as a starting point. */
     obb = pw->plotter.bounding_box;
     pw->plotter.bounding_box = pw->plotter.raw_bounding_box;

     /*
      * Is there is some data plot depending on this axis, make sure
      * we have a valid one.  Otherwise, if there is a displayed axis
      * connected to it, do a dummy AskRange so that frame axes can
      * get min/max set.
      */
     if (bbp->xmax > bbp->xmin) {
	  if (pp->xaxis)
	       AtAxisAskRange(pp->xaxis, &bbp->xmin, &bbp->xmax);
	  else
	       XtAppError(XtWidgetToApplicationContext((Widget) pw),
			  "AtPlotter has no X axis defined");
     }
     else if (pp->xaxis && ISDISPLAYED(pp->xaxis)) {
	  dummy_min = LIN_MIN;
	  dummy_max = LIN_MAX;
	  AtAxisAskRange(pp->xaxis, &dummy_min, &dummy_max);
     }
     if (bbp->ymax > bbp->ymin) {
	  if (pp->yaxis)
	       AtAxisAskRange(pp->yaxis, &bbp->ymin, &bbp->ymax);
	  else
	       XtAppError(XtWidgetToApplicationContext((Widget) pw),
			  "AtPlotter has no Y axis defined");
     }
     else if (pp->yaxis && ISDISPLAYED(pp->yaxis)) {
	  dummy_min = LIN_MIN;
	  dummy_max = LIN_MAX;
	  AtAxisAskRange(pp->yaxis, &dummy_min, &dummy_max);
     }

     if (bbp->x2max > bbp->x2min) {
	  if (pp->x2axis)
	       AtAxisAskRange(pp->x2axis, &bbp->x2min, &bbp->x2max);
	  else
	       XtAppError(XtWidgetToApplicationContext((Widget) pw),
			  "AtPlotter has no X2 axis defined");
     }
     else if (pp->x2axis && ISDISPLAYED(pp->x2axis)) {
	  dummy_min = LIN_MIN;
	  dummy_max = LIN_MAX;
	  AtAxisAskRange(pp->x2axis, &dummy_min, &dummy_max);
     }

     if (bbp->y2max > bbp->y2min) {
	  if (pp->y2axis)
	       AtAxisAskRange(pp->y2axis, &bbp->y2min, &bbp->y2max);
	  else
	       XtAppError(XtWidgetToApplicationContext((Widget) pw),
			  "AtPlotter has no Y2 axis defined");
     }
     else if (pp->y2axis && ISDISPLAYED(pp->y2axis)) {
	  dummy_min = LIN_MIN;
	  dummy_max = LIN_MAX;
	  AtAxisAskRange(pp->y2axis, &dummy_min, &dummy_max);
     }

#define dif(fld) (obb.fld != bbp->fld)
     changed = dif(xmin) || dif (xmax) || dif(x2min) || dif (x2max) ||
	       dif(ymin) || dif (ymax) || dif(y2min) || dif (y2max);
#undef dif

#ifdef TRACE
     fprintf(stderr,
	     "In DecideAxisValues, %s %.1f,%.1f %.1f,%.1f %.1f,%.1f %.1f,%.1f\n",
	     changed ? "Changed to" : "Kept",
	     bbp->xmin, bbp->xmax, bbp->ymin, bbp->ymax,
	     bbp->x2min, bbp->x2max, bbp->y2min, bbp->y2max);
#endif
     return changed;
}

/*
 *   Actually redraw the entire plot
 */

static void Redraw(pw, win, drw, region)
AtPlotterWidget pw;
Window win;
Drawable drw;
Region region;
{
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterLayout *lp = &pw->plotter.layout;
     int i;

     /* First, the title */
     if (pw->plotter.show_title && pw->plotter.title_text) {
	  AtTextDraw(XtDisplay(pw), win, drw, pw->plotter.title_gc,
		     pw->plotter.title_text, pw->plotter.layout.title_x,
		     pw->plotter.layout.title_y);
     }

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

     /* Now the plot area if needed */
     if (pw->core.background_pixel != pw->plotter.plotarea_color) {
	  if (region)
	       XSetRegion(XtDisplay(pw), pw->plotter.plotarea_gc, region);
	  XFillRectangle(XtDisplay(pw), drw, pw->plotter.plotarea_gc,
			 lp->x1, lp->y1, lp->width, lp->height);
	  if (region)
	       XSetClipMask(XtDisplay(pw), pw->plotter.plotarea_gc, None);
     }

     /* 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;
	       AtPlotDraw(rp->child, XtDisplay(pw), drw, region, False);
	  }
     } else {
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       if (!NTHCHILDISDISPLAYED(pw, i)) continue;
	       AtPlotDraw(CHILD(pw, i), XtDisplay(pw), drw, 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/pixmap if required.
 *   If rescale is required, calculate min/max of axes
 *        (so layout knows how wide axes are);
 *   Do global relayout if required.
 *   Get axis positions from layout and call layout callback if required.
 *        If there were any changes in axis positions set them.
 *   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(pw, event, region)
AtPlotterWidget pw;
XEvent *event;
Region region;
{
#define ev ((XExposeEvent *) event)
     AtPlotterPart *pp = &pw->plotter;
     Drawable drw;
     Window win;
     AtBusyCallbackData cbd;
     Boolean pixels_moved = False;
     Boolean numbers_moved = False;
     Boolean full_refresh = False;
     int i;

#ifdef TRACE
     fprintf(stderr,
	     "\nIn 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);
     fprintf(stderr, "layout = %d recalc = %d redraw = %d\n",
	     pp->layout_required, pp->rescale_required,
	     pp->redraw_required);
#endif

     /* Don't redraw when a synthetic events occurs! */
     if ( !pp->auto_redisplay && ev->send_event) {
	  pp->redisplay_required = True;
	  return;
     }

     /* Set the busy cursor */
     if (pp->use_cursors)
	  XDefineCursor(XtDisplay(pw), XtWindow(pw), pp->busy_cursor);

     /* Deliver the busy callback */
     if (HasBusyCB(pw)) {
	  cbd.reason = AtBusyPLOTTER;
	  cbd.busy = True;
	  XtCallCallbacks((Widget) pw, XtNbusyCallback, (XtPointer) &cbd);
     }
     else
	  cbd.busy = False;

     /* Get the pixmap if needed */
     if (pp->use_pixmap && pp->pixmap_required)
	  GetPixmap(pw);
     win = XtWindow(pw);
     drw = pp->use_pixmap ? pp->pixmap : win;

     if (pp->redisplay_required) {      /* Redraw all! */
	  full_refresh = True;
	  region = NULL;
     }
     else if (ev->send_event)           /* Is synthetic! */
	  region = NULL;

     /*
      * If the event covers (nearly) the whole window,
      * ignore the region (for speed!)
      */
     if (region) {
	  if (ev->x < 10 && ev->y < 10 &&
	      ev->width > pw->core.width - 20 &&
	      ev->height > pw->core.height - 20)
	       region = NULL;
	  full_refresh = True;
#ifdef TRACE
	  fprintf(stderr, "full refresh because of region + area\n");
#endif
     }
#undef ev

     /*
      * Come back to here if the length of an axis changed by > 25%
      * and the axis indicates tic_interval et al needs recalculating,
      * or if one of the calc routines has set rescale_required or
      * layout_required (probably an axis because something like the
      * number width has changed).
      */
recalc_again:
     pp->in_layout_mode = True;

     if (pp->rescale_required)
	  numbers_moved |= DecideAxisValues(pw);

     if (pp->layout_required || RecalcLegend(pw))
	  pixels_moved |= Layout(pw);

     /*
      * Get the current axis positions and call layout callback list procs
      * to give applications or other plotters the chance to do some axis
      * alignments if this is needed.
      */
     if (HasLayoutCB(pw)) {
	  AtAxisPositions ap;

	  GetAxisPositions(pw, &ap);
	  XtCallCallbacks((Widget) pw, XtNlayoutCallback, (XtPointer) &ap);
	  if (SetAxisPositions(pw, &ap))
		pixels_moved = pp->redraw_required = True;
     }
     /*
      * Layout of axis positions are set from axis position alignments
      */
     if (pp->position_required)
	  pixels_moved = pp->redraw_required = True;

     if (pixels_moved) {
	  AtPlotterLayout *lp = &pp->layout;
	  Boolean ti_changed = False;

#ifdef TRACE
	  fprintf(stderr, "Setting positions...\n");
#endif
	  /*
	   * With the y axes, must swap min and max values as window is
	   * measured 0=top, we want 0 to be bottom.
	   */
	  if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	       ti_changed |=
		    AtAxisSetPosition(pp->xaxis, lp->x1, lp->y2, lp->x2,
				      lp->y2, lp->y2 - lp->y1);
	  if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	       ti_changed |=
		    AtAxisSetPosition(pp->yaxis, lp->x1, lp->y2, lp->x1,
				      lp->y1, lp->x2 - lp->x1);
	  if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	       ti_changed |=
		    AtAxisSetPosition(pp->x2axis, lp->x1, lp->y1, lp->x2,
				      lp->y1, lp->y2 - lp->y1);
	  if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	       ti_changed |=
		    AtAxisSetPosition(pp->y2axis, lp->x2, lp->y2, lp->x2,
				      lp->y1, lp->x2 - lp->x1);
#ifdef TRACE
	  if (ti_changed) {
	       fprintf(stderr, "Redisplay: Axes length changed!\n");
	  }
#endif
	  if (ti_changed)
	       goto recalc_again;
     }

     pp->rescale_required = pp->layout_required = False;

     if (pixels_moved || numbers_moved) {
	  /* Need to rescale the entire graph */
#ifdef TRACE
	  fprintf(stderr, "Rescaling the lot, starting with axes\n");
#endif
	  /* Must do the axes first, as they may request recalc */
	  if (pp->xaxis && ISDISPLAYED(pp->xaxis))
	       AtPlotRecalc((AtPlotWidget) pp->xaxis, NULL, NULL, 0, 0);
	  if (pp->x2axis && ISDISPLAYED(pp->x2axis))
	       AtPlotRecalc((AtPlotWidget) pp->x2axis, NULL, NULL, 0, 0);
	  if (pp->yaxis && ISDISPLAYED(pp->yaxis))
	       AtPlotRecalc((AtPlotWidget) pp->yaxis, NULL, NULL, 0, 0);
	  if (pp->y2axis && ISDISPLAYED(pp->y2axis))
	       AtPlotRecalc((AtPlotWidget) pp->y2axis, NULL, NULL, 0, 0);

	  if (pp->layout_required || pp->rescale_required) {
#ifdef TRACE
	       fprintf(stderr, "Axis recalc forces goto\n");
#endif
	       goto recalc_again;
	  }

#ifdef TRACE
	  fprintf(stderr, "Now the children\n");
#endif
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       AtPlotWidget ch = CHILD(pw, i);
#define cha (AtAxisCoreWidget) ch

	       if ( !ISDISPLAYED(ch))
		    continue;
	       if (XtIsSubclass((Widget) ch, atAxisCoreWidgetClass)) {
		    if (cha != pp->xaxis && cha != pp->yaxis &&
			cha != pp->x2axis && cha != pp->y2axis) {
			 XtAppWarning(XtWidgetToApplicationContext((Widget) ch),
				      "AtAxisCore is displayed but not attached");
		    }
	       }
	       else {
		    AtPlotRecalc(ch,
				 AtAxisGetScale(USESX2AXIS(ch) ?
						pp->x2axis : pp->xaxis),
				 AtAxisGetScale(USESY2AXIS(ch) ?
						pp->y2axis : pp->yaxis),
				 0, -1);
	       }
	  }
     }
     else {
	  /* Not entire graph, perhaps individual chunks? */
#ifdef TRACE
	  fprintf(stderr, "Rescaling individual children\n");
#endif
	  for (i = 0; i < NUMCHILDREN(pw); i++) {
	       ExtendedList *ep;

	       if ( !NTHCHILDISDISPLAYED(pw, i) ||
		    !(ep = CONSTRAINT(pw, i)->plotter.extended_list))
		    continue;
	       while (ep) {
		    AtPlotRecalc(CHILD(pw, i),
				 AtAxisGetScale(USESX2AXIS(CHILD(pw, i)) ?
						pp->x2axis : pp->xaxis),
				 AtAxisGetScale(USESY2AXIS(CHILD(pw, i)) ?
						pp->y2axis : pp->yaxis),
				 ep->from, ep->to);
		    ep = ep->next;
	       }
	       CONSTRAINT(pw, i)->plotter.needs_refresh = True;
	  }
     }

     if (pp->layout_required || pp->rescale_required) {
#ifdef TRACE
	  fprintf(stderr, "something requested changes in recalc");
#endif
	  goto recalc_again;
     }

     /* First, clear the screen/pixmap if that has been requested */
     if (pp->redraw_required ||
	 pp->expose_requested && (pixels_moved || numbers_moved)) {
#ifdef TRACE
	  fprintf(stderr, "Clearing window due to pixel/numbers moved\n");
#endif
	  if (pp->use_pixmap)
	       XFillRectangle(XtDisplay(pw), pp->pixmap, pp->pixmap_gc,
			      0, 0, pp->pixmap_width, pp->pixmap_height);
	  else
	       XClearWindow(XtDisplay(pw), win);
	  region = NULL;      /* Be sure: unset region! */
     }

     if (pp->redraw_required || pixels_moved || numbers_moved || full_refresh) {
#ifdef TRACE
	  fprintf(stderr, "redrawing the lot.\n");
#endif
	  Redraw(pw, win, drw, region); /* Redraw the whole lot */

	  if (pp->can_slide)            /* May be, drag postions have changed */
	       SetDragPositions(pw);
     }
     else {
	  /* Perhaps one of the plots wants redrawing */
#ifdef TRACE
	  fprintf(stderr, "refreshing the children.\n");
#endif
	  if (pp->rank_children) {
	       Rank *rp;

	       for (rp = pp->ordered_children; rp; rp = rp->next) {
		    if ( !ISDISPLAYED(rp->child))
			 continue;
		    if (CONSTRAINTS(rp->child)->plotter.needs_refresh)
			 AtPlotDraw(rp->child, XtDisplay(pw), drw, region, True);
	       }
	  }
	  else {
	       for (i = 0; i < NUMCHILDREN(pw); i++) {
		    if ( !NTHCHILDISDISPLAYED(pw, i))
			 continue;
		    if (CONSTRAINT(pw, i)->plotter.needs_refresh)
			 AtPlotDraw(CHILD(pw, i), XtDisplay(pw), drw, region, True);
	       }
	  }
     }

     /* Now forget all requests */
     pp->redraw_required = pp->rescale_required = pp->layout_required = False;
     pp->expose_requested = False;

     for (i = 0; i < NUMCHILDREN(pw); i++) {
	  AtPlotterConstraints c = CONSTRAINT(pw, i);

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

     /* Reset state variables */
     pp->in_layout_mode = False;
     pp->redisplay_required = False;

     /* Now set the current axis positions and reset flag */
     GetAxisPositions(pw, &pw->plotter.positions);
     pp->position_required = False;

     /* Copy pixmap to window */
     if(pp->use_pixmap)
	  XCopyArea(XtDisplay(pw), drw, win, pp->pixmap_gc, 0, 0,
		    pp->pixmap_width, pp->pixmap_height, 0, 0);

     /* Deliver the busy callback */
     if (cbd.busy) {
	  cbd.reason = AtBusyPLOTTER;
	  cbd.busy = False;
	  XtCallCallbacks((Widget) pw, XtNbusyCallback, (XtPointer) &cbd);
     }

     /* Reset the current cursor */
     if (pp->use_cursors)
	  XDefineCursor(XtDisplay(pw), XtWindow(pw), pp->current_cursor);

#ifdef TRACE
     fprintf(stderr, "Done redisplay.\n\n");
#endif
}

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

static void Destroy(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;

     /* Free up the private data */
     FreeTitle(pw);
     FreeLegendTitle(pw);
     AtFontFamilyRelease(pp->ff);
     XtReleaseGC((Widget) pw, pp->title_gc);
     XtReleaseGC((Widget) pw, pp->legend_gc);
     XtReleaseGC((Widget) pw, pp->select_gc);
     XtReleaseGC((Widget) pw, pp->drag_gc);
     XtReleaseGC((Widget) pw, pp->plotarea_gc);

     /* Free our private copies of string resource */
     XtFree(pp->font_family);
     XtFree(pp->title);
     XtFree(pp->legend_title);

     /* Free the linked list of ordered_children */
     if (pp->ordered_children) {
	  Rank *tmp = pp->ordered_children;

	  while (tmp->next) {
	       tmp = tmp->next;
	       XtFree((char *) tmp->prev);
	  }
	  XtFree((char *) tmp);
     }

     /* Free the drag callback cursors */
     XFreeCursor(XtDisplay(pw), pp->top_left_cursor);
     XFreeCursor(XtDisplay(pw), pp->top_right_cursor);
     XFreeCursor(XtDisplay(pw), pp->bottom_left_cursor);
     XFreeCursor(XtDisplay(pw), pp->bottom_right_cursor);

     /* Free pixmap and pixmap gc */
     if (pp->pixmap)
	  FreePixmap(pw);
}

/*
 *   Resize
 */

static void Resize(pw)
AtPlotterWidget pw;
{
     if (XtWindow(pw))
	  LayoutRequired(pw);
     if (pw->plotter.use_pixmap)
	  pw->plotter.pixmap_required = True;
}

/*
 *   Realize
 */

static void Realize(w, vm, wa)
AtPlotterWidget w;
XtValueMask *vm;
XSetWindowAttributes *wa;
{
     (superclass->core_class.realize)((Widget) w, vm, wa);
     /* Calculate initial boundingbox */
     (void) NewRawBoundingBox(w);
     (void) RecalcLegend(w);
     /* Install enter/leave callbacks for internal cursors */
     if (w->plotter.use_cursors) {
	  InstallHandlers(w);
	  XDefineCursor(XtDisplay(w), XtWindow(w), w->plotter.plotter_cursor);
     }
}

/*
 *   SetValues
 */

static Boolean SetValues(current, request, new)
AtPlotterWidget current, request, new;
{
#define Changed(field) (new->plotter.field != current->plotter.field)
     AtPlotterPart *pp = &new->plotter;
     XGCValues gcv;
     Boolean redraw = False;
     Boolean layout = False;
     Boolean recalc_legend = False;
     Boolean rescale = False;
     Boolean mirror;
     int i;

     if (Changed(rank_children))
	  redraw = True;

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

     if (!Changed(legend_title) &&
	 (Changed(font_family) || Changed(legend_title_size) ||
	  Changed(legend_title_style))) {
	  AtTextReformat(pp->legend_title_text,
			 pp->ff, pp->legend_title_size, pp->legend_title_style);
	  recalc_legend = True;
     }

     if (Changed(title)) {
	  XtFree(current->plotter.title);
	  pp->title = AtNewString(pp->title);
	  FreeTitle(new);
	  GetTitle(new);
	  if (pp->show_title) {
	       layout = True;
	       redraw = True;
	  }
     }

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

     if (Changed(legend_size)) {
	  for (i = 0; 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, pp->title_gc);
	  gcv.foreground = pp->title_color;
	  pp->title_gc = XtGetGC((Widget) new, GCForeground, &gcv);
	  redraw = True;
     }

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

     if (Changed(legend_color) || new->core.background_pixel != current->core.background_pixel) {
	  XtReleaseGC((Widget) new, pp->select_gc);
	  gcv.foreground = new->core.background_pixel;
	  gcv.background = pp->legend_color;
	  pp->select_gc = XtGetGC((Widget) new, GCForeground|GCBackground, &gcv);
	  redraw = True;
     }

     if (Changed(plotarea_color)) {
	  XtReleaseGC((Widget) new, pp->plotarea_gc);
	  gcv.foreground = pp->plotarea_color;
	  pp->plotarea_gc = XtGetGC((Widget) new, GCForeground, &gcv);
	  redraw = True;
     }

     if (Changed(show_title)) {
	  layout = redraw = True;
     }

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

     if (Changed(legend_left)) {
	  if (pp->show_legend)
	       layout = redraw = True;
     }

     if (pp->show_legend &&
	 (Changed(default_legend_width) || Changed(legend_spacing))) {
	  recalc_legend = layout = redraw = True;
     }

     if (pp->show_title && Changed(default_title_height)) {
	  layout = redraw = True;
     }

     if (pp->show_legend && Changed(default_title_height)) {
	  recalc_legend = layout = redraw = True;
     }

     if (Changed(margin_width) || Changed(margin_height)) {
	  layout = True;
     }

     if (Changed(xaxis)) {
	  if (!XtIsSubclass((Widget) pp->xaxis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "AtPlotter needs a subclass of AtAxisCore as xAxis");
	       pp->xaxis = current->plotter.xaxis;
	  } else {
	       XtVaGetValues((Widget) pp->xaxis, XtNmirror,
			     (XtPointer) &mirror, NULL);
	       if (mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget) new),
				 "XAxis widget should have XtNmirror false");
		    XtVaSetValues((Widget) pp->xaxis, XtNmirror, False, NULL);
	       }
	       if (ISDISPLAYED(pp->xaxis)) {
		    layout = rescale = True;
	       }
	  }
     }
     if (Changed(yaxis)) {
	  if (!XtIsSubclass((Widget) pp->yaxis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "AtPlotter needs a subclass of AtAxisCore as yAxis");
	       pp->yaxis = current->plotter.yaxis;
	  } else {
	       XtVaGetValues((Widget) pp->yaxis, XtNmirror,
			     (XtPointer) &mirror, NULL);
	       if (mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget) new),
				 "YAxis widget should have XtNmirror false");
		    XtVaSetValues((Widget) pp->yaxis, XtNmirror, False, NULL);
	       }
	       if (ISDISPLAYED(pp->yaxis)) {
		    layout = rescale = True;
	       }
	  }
     }
     if (Changed(x2axis)) {
	  if (!XtIsSubclass((Widget) pp->x2axis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "AtPlotter needs a subclass of AtAxisCore as x2Axis");
	       pp->x2axis = current->plotter.x2axis;
	  } else {
	       XtVaGetValues((Widget) pp->x2axis, XtNmirror,
			     (XtPointer) &mirror, NULL);
	       if (!mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget) new),
				 "X2Axis widget should have XtNmirror True");
		    XtVaSetValues((Widget) pp->x2axis, XtNmirror, True, NULL);
	       }
	       if (ISDISPLAYED(pp->x2axis)) {
		    layout = rescale = True;
	       }
	  }
     }
     if (Changed(y2axis)) {
	  if (!XtIsSubclass((Widget) pp->y2axis, atAxisCoreWidgetClass)) {
	       XtAppWarning(XtWidgetToApplicationContext((Widget) new),
			    "AtPlotter needs a subclass of AtAxisCore as y2Axis");
	       pp->y2axis = current->plotter.y2axis;
	  } else {
	       XtVaGetValues((Widget) pp->y2axis, XtNmirror,
			     (XtPointer) &mirror, NULL);
	       if (!mirror) {
		    XtAppWarning(XtWidgetToApplicationContext((Widget) new),
				 "y2Axis widget should have XtNmirror True");
		    XtVaSetValues((Widget) pp->y2axis, XtNmirror, True, NULL);
	       }
	       if (ISDISPLAYED(pp->y2axis)) {
		    layout = rescale = True;
	       }
	  }
     }

     if (Changed(auto_redisplay)) {
	  if (pp->auto_redisplay)
	       redraw = True;
     }

     if (Changed(use_pixmap)) {
	  if (pp->use_pixmap)
		pp->pixmap_required = True;
	  else {
		pp->pixmap_required = False;
		if (pp->pixmap)
			FreePixmap(new);
	  }
	  redraw = True;
     }

     if (Changed(use_cursors)) {
	  if (pp->use_cursors)
	       InstallHandlers(new);
	  else
	       RemoveHandlers(new);
     }

     if (Changed(plotter_cursor) && XtIsRealized((Widget) new) && pp->use_cursors) {
	  XDefineCursor(XtDisplay(new), XtWindow(new), pp->plotter_cursor);
     }

     if (new->core.background_pixel != current->core.background_pixel) {
	  if (pp->use_pixmap)
	       GetPixmapGC(new);
     }

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

     return False;

#undef Changed
}

/*
 *   Query geometry
 */

static XtGeometryResult QueryGeometry(pw, req, rep)
AtPlotterWidget pw;
XtWidgetGeometry *req, *rep;
{
#ifdef DEBUG
     fprintf(stderr, "QueryGeometry: w=%d(c=%d) h=%d(c=%d) mode=%x\n",
	     req->width, pw->core.width, req->height, pw->core.height,
	     req->request_mode);
#endif
     rep->request_mode = CWWidth | CWHeight;
     rep->width = pw->core.width < DEFAULT_WIDTH
		  ? DEFAULT_WIDTH : pw->core.width;
     rep->height = pw->core.height < DEFAULT_HEIGHT
		   ? DEFAULT_HEIGHT : pw->core.height;
     if ( ((req->request_mode & (CWWidth | CWHeight)) == (CWWidth | CWHeight))
	  && rep->width == req->width && rep->height == req->height)
	  return XtGeometryYes;
     else if (rep->width == pw->core.width && rep->height == pw->core.height)
	  return XtGeometryNo;
     else
	  return XtGeometryAlmost;
}

/*
 *   Insert/delete child
 */

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

     /* Warn if child of wrong class */
     if (!XtIsSubclass(w, atPlotWidgetClass))
	  XtAppWarning(XtWidgetToApplicationContext(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);

     /*
      * Maintain this list even if no rank order is requested, so if
      * rank order is wanted later the list is ready.
      */
     RankOrderChildren(w);
     if (XtIsRealized((Widget) p)) {
	  if (RecalcLegend(p))
	       LayoutRequired(p);
	  if (NewRawBoundingBox(p))
	       RescaleRequired(p);
	  else
	       RedrawRequired(p);
     }
}

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

     int i;

     if (p->plotter.selected_item != NO_ITEM) {
	  for (i = 0; i < NUMCHILDREN(p); i++)
	       if (w == (Widget) CHILD(p, i))
		    break;
	  if (i == p->plotter.selected_item)
	       p->plotter.selected_item = NO_ITEM;
	  else if (i < p->plotter.selected_item)
	       p->plotter.selected_item--;
     }

     /* Call the superclass's delete_child proc to actually delete the child */
     (*superclass->composite_class.delete_child)(w);
     RankOrderRemove(w);
     if (XtIsRealized((Widget) p)) {
	  if (RecalcLegend(p))
	       LayoutRequired(p);
	  if (NewRawBoundingBox(p))
	       RescaleRequired(p);
	  else
	       RedrawRequired(p);
     }
}

/*
 *   Constraint initialize/destroy/set values
 */

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;
     c->plotter.extended_list = NULL;
     c->plotter.needs_refresh = False;
}

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) || Changed(use_x2_axis)) {
	  redraw = True;
	  rescale = NewRawBoundingBox(parent);
	  if (Changed(displayed) && XtIsSubclass(new, atAxisCoreWidgetClass))
	       layout = rescale = 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);

     return False;

#undef Changed
}

/*
 *   The routines for handling the legend
 *
 *   Recalculate the layout of the legend,
 *   return True if the width or the height changed.
 */

static Boolean RecalcLegend(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterConstraints c;
     int h = 0, w = 0, i;
     Boolean changed;

     if (pp->show_legend == False)
	  return False;

     h = AtTextHeight(pp->legend_title_text) + pp->margin_height;
     for(i = 0; i < NUMCHILDREN(pw); i++) {
	  if ( !NTHCHILDISDISPLAYED(pw, i))
	       continue;
	  c = CONSTRAINT(pw, i);
	  if (c->plotter.legend_text != NULL) {
	       h += AtTextHeight(c->plotter.legend_text) + pp->legend_spacing;
	       w = Max(w, AtTextWidth(c->plotter.legend_text));
	  }
     }
     if (pp->default_legend_width > 0)
	  w = pp->default_legend_width;
     else {
	  w += ICON_WIDTH + 2 * pp->margin_width;
	  w = Max(w, AtTextWidth(pp->legend_title_text));
     }
     changed = (w != pp->layout.legend_width) || (h != pp->layout.legend_height);
     pp->layout.legend_height = h;
     pp->layout.legend_width = w;

     return changed;
}

static void RedrawLegend(w, region, copy)
AtPlotterWidget w;
Region region;
Boolean copy;
{
     AtPlotterPart *pp = &w->plotter;
     AtPlotterLayout *lp = &w->plotter.layout;
     AtText *t;
     Window win;
     Drawable drw;
     GC gc;
     int y, i;

     if (pp->show_legend == False)
	  return;

     win = XtWindow(w);
     drw = pp->use_pixmap ? pp->pixmap : win;

     if (region) {
	  XSetRegion(XtDisplay(w), pp->legend_gc, region);
	  XSetRegion(XtDisplay(w), pp->select_gc, region);
     }

     XFillRectangle(XtDisplay(w), drw, pp->select_gc,
		   lp->legend_x - 1, lp->legend_y - 1,
		   lp->legend_width + 3, lp->legend_height + 3);

     y = lp->legend_y;
     AtTextDrawJustified(XtDisplay(w), win, drw, pp->legend_gc,
			 pp->legend_title_text,
			 AtTextJUSTIFY_CENTER, AtTextJUSTIFY_CENTER,
			 lp->legend_x, y,
			 lp->legend_width,
			 AtTextHeight(pp->legend_title_text));

     y += AtTextHeight(pp->legend_title_text) + pp->margin_height;
     for(i = 0; i < NUMCHILDREN(w); i++ ) {
	  if ( !NTHCHILDISDISPLAYED(w, i))
	       continue;
	  if (t = CONSTRAINT(w, i)->plotter.legend_text) {
	       if (i == pp->selected_item) {
		    XFillRectangle(XtDisplay(w), drw, pp->legend_gc,
				   lp->legend_x+2*pp->margin_width+ICON_WIDTH-1,
				   y,
				   lp->legend_width-2*pp->margin_width-ICON_WIDTH+2,
				   AtTextHeight(t));
		    gc = pp->select_gc;
		    pp->selected_item_y = y;
	       }
	       else
		    gc = pp->legend_gc;
	       AtTextDraw(XtDisplay(w), win, drw, gc, t,
			  lp->legend_x+2*pp->margin_width+ICON_WIDTH,
			  y + AtTextAscent(t));
	       AtPlotDrawIcon(CHILD(w, i), XtDisplay(w), drw,
			      lp->legend_x, y, ICON_WIDTH,
			      AtTextHeight(t), region);
	       y += AtTextHeight(t) + pp->legend_spacing;
	  }
     }

     if (copy)
	  XCopyArea(XtDisplay(w), drw, win, pp->pixmap_gc,
		    lp->legend_x-1, lp->legend_y-1,
		    lp->legend_width+3, lp->legend_height+2,
		    lp->legend_x-1, lp->legend_y-1);

     if (region) {
	  XSetClipMask(XtDisplay(w), pp->legend_gc, None);
	  XSetClipMask(XtDisplay(w), pp->select_gc, None);
     }
}

/*
 *   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(w)
Widget w;
{
     AtPlotterWidget parent = ParentPlotter(w);
     AtPlotterConstraints pcons = (AtPlotterConstraints) w->core.constraints;
     Rank *locate, *newnode;
     Boolean found = False;

     if ( !XtIsSubclass(w, atPlotWidgetClass))    /* Don't rank axes */
	  return;

     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(w)
Widget w;
{
     RankOrderRemove(w);
     RankOrderChildren(w);
}

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

     if (!XtIsSubclass(w, atPlotWidgetClass))     /* Don't rank axes */
	  return;

     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

/*
 *      Callback routines
 *
 *      A lot of ideas and algorithms for the motion and drag callback
 *      procs are taken from plotter v4 by Chris Craig a.o.
 */

#define UseCursors(pw)   (pw->plotter.use_cursors)
#define InSelect(pw)     (pw->plotter.in_select)
#define InClick(pw)      (pw->plotter.in_click)
#define InDrag(pw)       (pw->plotter.in_drag)
#define InSlide(pw)      (pw->plotter.in_slide)
#define CanSlide(pw)     (pw->plotter.can_slide)
#define InLegend(pw)     (pw->plotter.in_legend)

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

#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 AdjustXY(pw, event, x, y) \
     x = event->x;\
     y = event->y;\
     if      (x < pw->plotter.layout.x1) x = pw->plotter.layout.x1;\
     else if (x > pw->plotter.layout.x2) x = pw->plotter.layout.x2;\
     if      (y < pw->plotter.layout.y1) y = pw->plotter.layout.y1;\
     else if (y > pw->plotter.layout.y2) y = pw->plotter.layout.y2

#define InDragRange(pw) ((pw->plotter.drag_width*pw->plotter.drag_height) > 36)

#define SetPointer(pw, x, y) \
     XWarpPointer(XtDisplay(pw), None, XtWindow(pw), 0, 0, 0, 0, x, y)

#define ResetStates(pw) pw->plotter.in_select = pw->plotter.in_click = \
			pw->plotter.in_drag = pw->plotter.in_slide = \
			pw->plotter.in_legend =  False

#define X1Scale(pw, x) AtScalePixelToUser(pw->plotter.xaxis->axiscore.scale, x)
#define Y1Scale(pw, y) AtScalePixelToUser(pw->plotter.yaxis->axiscore.scale, y)
#define X2Scale(pw, x) AtScalePixelToUser(pw->plotter.x2axis->axiscore.scale, x)
#define Y2Scale(pw, y) AtScalePixelToUser(pw->plotter.y2axis->axiscore.scale, y)

#define DefCursor(pw, cursor) XDefineCursor(XtDisplay(pw), XtWindow(pw), cursor)
#define UndefCursor(pw) XDefineCursor(XtDisplay(pw), XtWindow(pw), pw->plotter.current_cursor)

#define CM_NONE     0x00
#define CM_TOP      0x10
#define CM_BOTTOM   0x20
#define CM_LEFT     0x01
#define CM_RIGHT    0x02
#define CM_TOPLEFT  0x11
#define CM_TOPRIGHT 0x12
#define CM_BOTLEFT  0x21
#define CM_BOTRIGHT 0x22
#define CM_SLIDE    0x30

/*
 *   Internal enter/leave callback procs
 */

/* Install enter/leave callback proc */
static void InstallHandlers(w)
AtPlotterWidget w;
{
     XtAddEventHandler((Widget) w, EnterWindowMask, False,
		       (XtEventHandler) EnterLeave, (XtPointer) True);
     XtAddEventHandler((Widget) w, LeaveWindowMask, False,
		       (XtEventHandler) EnterLeave, (XtPointer) False);
}

/* Remove enter/leave callback proc */
static void RemoveHandlers(w)
AtPlotterWidget w;
{
     XtRemoveEventHandler((Widget) w, EnterWindowMask, False,
			  (XtEventHandler) EnterLeave, (XtPointer) True);
     XtRemoveEventHandler((Widget) w, LeaveWindowMask, False,
			  (XtEventHandler) EnterLeave, (XtPointer) False);
}

/* Enter/leave callback: set the appropriate cursor */
static void EnterLeave(pw, enter, ev, cont)
AtPlotterWidget pw;
XtPointer enter;
XEvent *ev;
int cont;
{
     AtPlotterPart *pp = &pw->plotter;

     if (UseCursors(pw) && HasMotionCB(pw)) {
	  if (enter) {
	       pp->current_cursor = pp->motion_cursor;
	       DefCursor(pw, pp->motion_cursor);
	  }
	  else {
	       pp->current_cursor = pp->plotter_cursor;
	       UndefCursor(pw);
	  }
     }
}

/*
 *   Motion callback procs
 */

/* Set up motion callback data and call callback list procs */
static void SendMotionCallback P((AtPlotterWidget, int, int));
static void SendMotionCallback(pw, x, y)
AtPlotterWidget pw;
int x, y;
{
     AtPointCallbackData data;

     data.reason = AtPointMOTION;
     data.pixelx = x;
     data.pixely = y;
     data.x1 = X1Scale(pw, x);
     data.y1 = Y1Scale(pw, y);
     data.x2 = pw->plotter.x2axis ? X2Scale(pw, x) : 0.0;
     data.y2 = pw->plotter.y2axis ? Y2Scale(pw, y) : 0.0;
     XtCallCallbacks((Widget) pw, XtNmotionCallback, (XtPointer) &data);
}

/* Motion callback handler: check callback/range and send callback */
static void HandleMotion(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
     int x, y;

     AdjustXY(pw, event, x, y);
     if (HasMotionCB(pw) && InPlottingRegion(pw, event))
	  SendMotionCallback(pw, x, y);
}

/*
 *   Selection (drag and click) callback procs
 */

/* Calc and set drag positions/dimensions, i.e. after redisplay orresize */
static void SetDragPositions(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     AtPlotterLayout *lp = &pw->plotter.layout;
     int x, y;

     if (X1Scale(pw, lp->x1) > pp->slide_x1 || X1Scale(pw, lp->x2) < pp->slide_x2 ||
	 Y1Scale(pw, lp->y2) > pp->slide_y1 || Y1Scale(pw, lp->y1) < pp->slide_y2) {
	  pp->can_slide = False;
	  return;
     }
     pp->drag_x = AtScaleUserToPixel(pp->xaxis->axiscore.scale, pp->slide_x1);
     x = AtScaleUserToPixel(pp->xaxis->axiscore.scale, pp->slide_x2);
     pp->drag_y = AtScaleUserToPixel(pp->yaxis->axiscore.scale, pp->slide_y2);
     y = AtScaleUserToPixel(pp->yaxis->axiscore.scale, pp->slide_y1);
     pp->drag_width  = x - pp->drag_x;
     pp->drag_height = y - pp->drag_y;
}

/* Draw the drag rectangle, set the appropriate cursor */
static void DrawDragRect P((AtPlotterWidget));
static void DrawDragRect(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     Cursor cursor;
     int x, y;
     unsigned int w, h;
     short cm;

     cm = CM_NONE;
     if (pp->drag_width < 0) {
	  w = -pp->drag_width;
	  x = pp->drag_x - w;
	  cm |= CM_LEFT;
     }
     else {
	  w = pp->drag_width;
	  x = pp->drag_x;
	  cm |= CM_RIGHT;
     }
     if (pp->drag_height < 0) {
	  h = -pp->drag_height;
	  y = pp->drag_y - h;
	  cm |= CM_TOP;
     }
     else {
	  h = pp->drag_height;
	  y = pp->drag_y;
	  cm |= CM_BOTTOM;
     }
     if (InSlide(pw))
	  cm = CM_SLIDE;

     XDrawRectangle(XtDisplay(pw), XtWindow(pw), pp->drag_gc, x, y, w, h);

     if (UseCursors(pw)) {
	  if(cm != pp->cursor_mask) {
	       switch(cm) {
		    case CM_TOPLEFT:
			 cursor = pp->top_left_cursor;
			 break;
		    case CM_TOPRIGHT:
			 cursor = pp->top_right_cursor;
			 break;
		    case CM_BOTLEFT:
			 cursor = pp->bottom_left_cursor;
			 break;
		    case CM_BOTRIGHT:
			 cursor = pp->bottom_right_cursor;
			 break;
		    case CM_SLIDE:
			 cursor = pp->slide_cursor;
			 break;
	       }
	       DefCursor(pw, cursor);
	       pp->cursor_mask = cm;
	  }
     }
}

/* Erase the drag rectangle */
#define EraseDragRect(pw) DrawDragRect(pw)

/* Fill up click callback data and call callback proc list */
static void SendClickCallback P((AtPlotterWidget, int, int));
static void SendClickCallback(pw, x, y)
AtPlotterWidget pw;
int x, y;
{
     AtPointCallbackData data;

     data.reason = AtPointCLICK;
     data.pixelx = x;
     data.pixely = y;
     data.x1 = X1Scale(pw, x);
     data.y1 = Y1Scale(pw, y);
     data.x2 = pw->plotter.x2axis ? X2Scale(pw, x) : 0.0;
     data.y2 = pw->plotter.y2axis ? Y2Scale(pw, y) : 0.0;
     XtCallCallbacks((Widget) pw, XtNclickCallback, (XtPointer) &data);
}

/* Draw the legend rectangle */
static void DrawLegendRect P((AtPlotterWidget));
static void DrawLegendRect(pw)
AtPlotterWidget pw;
{
     AtPlotterPart *pp = &pw->plotter;
     AtText *t;

     if(t = CONSTRAINT(pw, pp->legend_item)->plotter.legend_text) {
	  XDrawRectangle(XtDisplay(pw), XtWindow(pw), pp->drag_gc,
			 pp->layout.legend_x - 1,
			 pp->legend_item_y - 1,
			 pp->layout.legend_width + 2,
			 AtTextHeight(t) + 1);
     }
}

/* Erase the legend rectangle */
#define EraseLegendRect(pw) DrawLegendRect(pw)

/* Select legend item */
static void SelectInLegend P((AtPlotterWidget, XButtonPressedEvent *));
static void SelectInLegend(pw, event)
AtPlotterWidget pw;
XButtonPressedEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     AtText *t;
     int i, y, y1, y2;

     y  = pp->layout.legend_y + AtTextHeight(pp->legend_title_text) +
	  pp->margin_height;
     y1 = y - pp->legend_spacing / 2;

     /* If in title region: deselect item */
     if (event->y < y1) {
	  pp->legend_item = NO_ITEM;
	  return;
     }

     /* Search for item to select */
     for (i = 0; i < NUMCHILDREN(pw); i++) {
	  if ( !NTHCHILDISDISPLAYED(pw, i))
	       continue;
	  if(t = CONSTRAINT(pw, i)->plotter.legend_text) {
	       y2 = y1 + AtTextHeight(t) + pp->legend_spacing;
	       if (event->y >= y1 && event->y <= y2)
		    break;
	       y1 += AtTextHeight(t) + pp->legend_spacing;
	       y  += AtTextHeight(t) + pp->legend_spacing;
	  }
     }

     /* Select item if found */
     if (i < NUMCHILDREN(pw)) {
	  pp->legend_item = i;
	  pp->legend_item_y = y;
	  DrawLegendRect(pw);
     }
}

/* Start selection for drag and click callback */
static void StartSelection(pw, event)
AtPlotterWidget pw;
XButtonPressedEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;

     ResetStates(pw);

     if (InPlottingRegion(pw, event)) {
	  if (HasClickCB(pw)) {
	       pp->in_click = True;
	       if (UseCursors(pw)) {
		    DefCursor(pw, pp->click_cursor);
	       }
	  }
	  if (HasDragCB(pw)) {
	       AdjustXY(pw, event, x, y);
	       pp->drag_x = x;
	       pp->drag_y = y;
	       pp->in_select = True;
	       pp->in_drag = False;
	       if (UseCursors(pw)) {
		    DefCursor(pw, pp->drag_cursor);
	       }
	  }
	  if (HasMotionCB(pw)) {
	       AdjustXY(pw, event, x, y);
	       SendMotionCallback(pw, x, y);
	  }
     }
     else if (HasSelectCB(pw) && InLegendRegion(pw, event)) {
	  if (UseCursors(pw)) {
	       DefCursor(pw, pp->select_cursor);
	  }
	  pp->in_legend = True;
	  pp->legend_item = pp->selected_item;
	  pp->legend_item_y = pp->selected_item_y;
	  SelectInLegend(pw, event);
     }
}

/* Drag handler */
static void Drag(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;

     if (InSelect(pw)) {
	  if (InDrag(pw))
	       EraseDragRect(pw);
	  AdjustXY(pw, event, x, y);
	  if ( !InPlottingRegion(pw, event))
	       SetPointer(pw, x, y);
	  pp->drag_width  = x - pp->drag_x;
	  pp->drag_height = y - pp->drag_y;
	  DrawDragRect(pw);
	  pp->in_drag = True;
     }
     else if (InLegend(pw)) {
	  if (pp->legend_item != NO_ITEM)
	       EraseLegendRect(pw);
	  if (InLegendRegion(pw, event))
	       SelectInLegend(pw, (XButtonPressedEvent *) event);
	  else {
	       if (UseCursors(pw)) {
		    UndefCursor(pw);
		    pp->cursor_mask = CM_NONE;
	       }
	       RedrawLegend(pw, NULL, pp->use_pixmap);
	       pp->legend_item = NO_ITEM;
	       pp->in_legend = False;
	  }
     }

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}

/* End selection: check and call drag/click callback list procs */
static void EndSelection(pw, event)
AtPlotterWidget pw;
XButtonReleasedEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     AtRectangleCallbackData recd;
     AtSelectCallbackData seld;
     int x, y;

     if (InSelect(pw)) {
	  if (InDrag(pw))
	       EraseDragRect(pw);
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pp->cursor_mask = CM_NONE;
	  }
	  if (InDrag(pw) && InDragRange(pw)) {
	       AdjustXY(pw, event, x, y);
	       if ( !InPlottingRegion(pw, event))
		    SetPointer(pw, x, y);
		recd.reason  = AtRectangleDRAG;
		recd.pixelx1 = Min(pp->drag_x, x);
		recd.pixelx2 = Max(pp->drag_x, x);
		recd.pixely1 = Max(pp->drag_y, y);
		recd.pixely2 = Min(pp->drag_y, y);
		recd.x11 = X1Scale(pw, recd.pixelx1);
		recd.x12 = X1Scale(pw, recd.pixelx2);
		recd.y11 = Y1Scale(pw, recd.pixely1);
		recd.y12 = Y1Scale(pw, recd.pixely2);
		if (pw->plotter.x2axis) {
		    recd.x21 = X2Scale(pw, recd.pixelx1);
		    recd.x22 = X2Scale(pw, recd.pixelx2);
		}
		else
		    recd.x21 = recd.x22 = 0.0;
		if (pw->plotter.y2axis) {
		    recd.y21 = Y2Scale(pw, recd.pixely1);
		    recd.y22 = Y2Scale(pw, recd.pixely2);
		}
		else
		   recd.y21 = recd.y22 = 0.0;
		pw->plotter.slide_x1 = recd.x11;
		pw->plotter.slide_x2 = recd.x12;
		pw->plotter.slide_y1 = recd.y11;
		pw->plotter.slide_y2 = recd.y12;
		pw->plotter.can_slide = True;
		XtCallCallbacks((Widget) pw, XtNdragCallback, (XtPointer) &recd);
	  }
     }
     else if (InLegend(pw)) {
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pp->cursor_mask = CM_NONE;
	  }
	  if (InLegendRegion(pw, event)) {
	       pp->selected_item = pp->legend_item;
	       pp->selected_item_y = pp->legend_item_y;
	       if (pp->selected_item != NO_ITEM) {
		    seld.reason = AtSelectSELECTED;
		    seld.widget = (Widget) CHILD(pw, pp->selected_item);
	       }
	       else {
		    seld.reason = AtSelectDESELECTED;
		    seld.widget = NULL;
	       }
	       XtCallCallbacks((Widget) pw, XtNselectCallback, (XtPointer) &seld);
	  }
	  RedrawLegend(pw, NULL, pp->use_pixmap);
	  pp->legend_item = NO_ITEM;
     }

     if (InClick(pw)) {
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pp->cursor_mask = CM_NONE;
	  }
	  if (InPlottingRegion(pw, event)) {
	       AdjustXY(pw, event, x, y);
	       SendClickCallback(pw, x, y);
	  }
     }

     ResetStates(pw);

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}

/*
 *   Slide callback procs
 */

/* Setup callback data and call slide callback list procs */
static void SendSlideCallback P((AtPlotterWidget, int, int));
static void SendSlideCallback(pw, x, y)
AtPlotterWidget pw;
int x, y;
{
     AtPlotterPart *pp = &pw->plotter;
     AtRectangleCallbackData data;

     data.reason  = AtRectangleSLIDE;
     data.pixelx1 = x;
     data.pixelx2 = x + pp->drag_width;
     data.pixely1 = y + pp->drag_height;
     data.pixely2 = y;
     data.x11 = X1Scale(pw, data.pixelx1);
     data.x12 = X1Scale(pw, data.pixelx2);
     data.y11 = Y1Scale(pw, data.pixely1);
     data.y12 = Y1Scale(pw, data.pixely2);
     if (pw->plotter.x2axis) {
	  data.x21 = X2Scale(pw, data.pixelx1);
	  data.x22 = X2Scale(pw, data.pixelx2);
     }
     else
	  data.x21 = data.x22 = 0.0;
     if (pw->plotter.y2axis) {
	  data.y21 = Y2Scale(pw, data.pixely1);
	  data.y22 = Y2Scale(pw, data.pixely2);
     }
     else
	  data.y21 = data.y22 = 0.0;
     pw->plotter.slide_x1 = data.x11;
     pw->plotter.slide_x2 = data.x12;
     pw->plotter.slide_y1 = data.y11;
     pw->plotter.slide_y2 = data.y12;
     XtCallCallbacks((Widget) pw, XtNslideCallback, (XtPointer) &data);
}

/* Start sliding */
static void StartSliding(pw, event)
AtPlotterWidget pw;
XButtonPressedEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;

     ResetStates(pw);

     if (CanSlide(pw) && HasSlideCB(pw) && InPlottingRegion(pw, event)) {
	  SetPointer(pw, pp->drag_x, pp->drag_y);
	  pp->in_slide = True;
	  DrawDragRect(pw);
     }

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}

/* Slide handler: check, setup callback data and call slide callback procs */
static void Slide(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;
     Boolean set = False;

     if (InSlide(pw)) {
	  EraseDragRect(pw);
	  AdjustXY(pw, event, x, y);
	  if ((x + pp->drag_width) > pp->layout.x2) {
	       pp->drag_x = x = pp->layout.x2 - pp->drag_width;
	       set = True;
	  }
	  else
	       pp->drag_x = x;
	  if ((y + pp->drag_height) > pp->layout.y2) {
	       pp->drag_y = y = pp->layout.y2 - pp->drag_height;
	       set = True;
	  }
	  else
	       pp->drag_y = y;
	  if (set || !InPlottingRegion(pw, event))
	       SetPointer(pw, x, y);
	  pp->in_slide = True;
	  DrawDragRect(pw);
	  SendSlideCallback(pw, x, y);
     }

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}

/* End sliding: check and call slide callback list procs */
static void EndSliding(pw, event)
AtPlotterWidget pw;
XButtonReleasedEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;

     if (InSlide(pw)) {
	  EraseDragRect(pw);
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pp->cursor_mask = CM_NONE;
	  }
	  AdjustXY(pw, event, x, y);
	  if ( !InPlottingRegion(pw, event))
	       SetPointer(pw, x, y);
	  if ((x + pp->drag_width) > pp->layout.x2)
	       pp->drag_x = pp->layout.x2 - pp->drag_width;
	  else
	       pp->drag_x = x;
	  if ((y + pp->drag_height) > pp->layout.y2)
	       pp->drag_y = pp->layout.y2 - pp->drag_height;
	  else
	       pp->drag_y = y;
	  pp->can_slide = True;
	  SendSlideCallback(pw, x, y);
     }

     ResetStates(pw);

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}

/* Cancel all actions */
static void Cancel(pw, event)
AtPlotterWidget pw;
XMotionEvent *event;
{
     AtPlotterPart *pp = &pw->plotter;
     int x, y;

     if (InSelect(pw) || InDrag(pw) || InSlide(pw)) {
	  EraseDragRect(pw);
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pw->plotter.cursor_mask = CM_NONE;
	  }
     }
     if (InLegend(pw)) {
	  if (UseCursors(pw)) {
	       UndefCursor(pw);
	       pw->plotter.cursor_mask = CM_NONE;
	  }
	  RedrawLegend(pw, NULL, pp->use_pixmap);
     }

     ResetStates(pw);

     if (HasMotionCB(pw) && InPlottingRegion(pw, event)) {
	  AdjustXY(pw, event, x, y);
	  SendMotionCallback(pw, x, y);
     }
}
