static char RCSid[] = "$Id: AxisCore.c,v 1.0 91/08/22 15:33:34 gnb Exp $"; 
/*
 * $Source: /export/data/sources/x/At/Plotter/RCS/AxisCore.c,v $
 * 
 * $Log:	AxisCore.c,v $
 * Revision 1.0  91/08/22  15:33:34  gnb
 * Initial revision
 * 
 * 
 */

/*

Copyright 1991 by Burdett, Buckeridge & Young Ltd.

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 Burdett, Buckeridge &
Young Ltd. (BBY) not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.

BBY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
BBY 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.

*/
/*
 * This contains the heart of the pixel fiddling code for the axes.
 * It is a meta class that relies on subclass methods to decide where
 * and what to label the axes, and then handles all the calculations,
 * redisplays etc internally.
 *
 * Should not be instansiated, but will sort-of work if required.
 */

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

#include <assert.h>

#ifdef _AtDevelopment_
#include "AxisCoreP.h"
#include "AtConverters.h"
#else
#include <At/AxisCoreP.h>
#include <At/AtConverters.h>
#endif

#define MARGIN 2	  /* Pixels between elemnts - will be a resource */
#define PS_MARGIN 6	  /* Same for PS */

static void AxisCoreClassPartInit P((WidgetClass));
static void AxisCoreClassInit P((WidgetClass));
static void AxisCoreInitialize P((AtAxisCoreWidget, AtAxisCoreWidget));
static void AxisCoreDestroy P((AtAxisCoreWidget));
static Boolean AxisCoreSetValues P((AtAxisCoreWidget, AtAxisCoreWidget,
				  AtAxisCoreWidget));

static void AxisCoreDraw P((AtPlotWidget, Display *, Window, Region,
			    int refresh));

static void AxisCoreRecalc P((AtPlotWidget, AtScale *, AtScale *, 
			      int, int));

static void AxisCoreRangeProc P((AtAxisCoreWidget, double *minp,
				 double *maxp, double *tip, 
				 int *nwp));
static void AxisCoreCalcProc P((AtAxisCoreWidget));

/* A helper routine for handling labels */
static void ReformatLabels P((AtAxisCoreWidget, int));
static void CalcAxisWidth P((AtAxisCoreWidget));

#define off(fld) XtOffsetOf(AtAxisCoreRec, axiscore.fld)
static double zero = 0.0, one = 1.0;

static XtResource resources[] = {
  {
       XtNmax, XtCMax, XtRDouble, sizeof(double), 
       off(max), XtRDouble, (XtPointer) &one
  },
  {
       XtNmin, XtCMin, XtRDouble, sizeof(double),
       off(min), XtRDouble, (XtPointer) &zero
  },
  {
       XtNticInterval, XtCTicInterval, XtRDouble, sizeof(double), 
       off(tic_interval), XtRImmediate, (XtPointer) &one
  },
  {
       XtNvertical, XtCVertical, XtRBoolean, sizeof(Boolean),
       off(vertical), XtRImmediate, (XtPointer) False
  },
  {
       XtNmirror, XtCMirror, XtRBoolean, sizeof(Boolean),
       off(mirror), XtRImmediate, (XtPointer) False
  },
  {
       XtNrangeCallback, XtCCallback, XtRCallback,
       sizeof(XtCallbackList), off(range_callback), XtRImmediate, NULL
  },
  {
       XtNlabel, XtCLabel, XtRString, sizeof(String),
       off(label), XtRImmediate, NULL
  },
  {
       XtNfontFamily, XtCFontFamily, XtRString, sizeof(String),
       off(font_family), XtRImmediate, NULL /*XtRCallProc, (XtPointer)defFF*/
  },
  {
       XtNlabelSize, XtCFontSize, XtRFontSize, sizeof(int),
       off(label_size), XtRImmediate, (XtPointer) AtFontNORMAL
  },
  {
       XtNlabelStyle, XtCFontStyle, XtRFontStyle, sizeof(int),
       off(label_style), XtRImmediate, (XtPointer) AtFontPLAIN
  },
  {
       XtNlabelColor, XtCForeground, XtRPixel, sizeof(Pixel),
       off(label_color), XtRString, XtDefaultForeground 
  },
  {
       XtNnumberSize, XtCFontSize, XtRFontSize, sizeof(int),
       off(number_size), XtRImmediate, (XtPointer) AtFontSMALL
  },
  {
       XtNnumberStyle, XtCFontStyle, XtRFontStyle, sizeof(int),
       off(number_style), XtRImmediate, (XtPointer) AtFontPLAIN
  },
  {
       XtNnumberColor, XtCForeground, XtRPixel, sizeof(Pixel),
       off(number_color), XtRString, XtDefaultForeground 
  },
  {
       XtNticsInside, XtCTicsInside, XtRBoolean, sizeof(Boolean),
       off(tics_inside), XtRImmediate, (XtPointer) False
  },
  {
       XtNticsOutside, XtCTicsOutside, XtRBoolean, sizeof(Boolean),
       off(tics_outside), XtRImmediate, (XtPointer) True
  },
  {
       XtNticLength, XtCTicLength, XtRShort, sizeof(short),
       off(tic_length), XtRImmediate, (XtPointer) 5
  },
  {
       XtNsubticLength, XtCTicLength, XtRShort, sizeof(short),
       off(subtic_length), XtRImmediate, (XtPointer) 2
  },
  {
       XtNdrawNumbers, XtCDrawNumbers, XtRBoolean, sizeof(Boolean),
       off(draw_numbers), XtRImmediate, (XtPointer) True
  },
  {
       XtNnumbersOutside, XtCNumbersOutside, XtRBoolean, sizeof(Boolean),
       off(numbers_outside), XtRImmediate, (XtPointer) True
  },
  {
       XtNdrawGrid, XtCDrawGrid, XtRBoolean, sizeof(Boolean),
       off(draw_grid), XtRImmediate, (XtPointer) True
  }
};
#undef off

AtAxisCoreClassRec atAxisCoreClassRec = {
  { /* core fields */
     /* superclass		*/	(WidgetClass) &atPlotClassRec,
     /* class_name		*/	"AtAxisCore",
     /* widget_size		*/	sizeof(AtAxisCoreRec),
     /* class_initialize	*/	(XtProc) AxisCoreClassInit,
     /* class_part_initialize	*/	(XtWidgetClassProc) AxisCoreClassPartInit,
     /* class_inited		*/	FALSE,
     /* initialize		*/	(XtInitProc) AxisCoreInitialize,
     /* initialize_hook		*/	NULL,
     /* pad			*/	NULL,
     /* pad			*/	NULL,
     /* pad			*/	0,
     /* resources		*/	resources,
     /* num_resources		*/	XtNumber(resources),
     /* xrm_class		*/	NULLQUARK,
     /* pad			*/	FALSE,
     /* pad			*/	FALSE,
     /* pad			*/	FALSE,
     /* pad			*/	FALSE,
     /* destroy			*/	(XtWidgetProc) AxisCoreDestroy,
     /* pad			*/	NULL,
     /* pad			*/	NULL,
     /* set_values		*/	(XtSetValuesFunc) AxisCoreSetValues,
     /* set_values_hook		*/	NULL,
     /* pad			*/	NULL,
     /* get_values_hook		*/	NULL,
     /* pad			*/	NULL,
     /* version			*/	XtVersion,
     /* callback_private	*/	NULL,
     /* pad			*/	NULL,
     /* pad			*/	NULL,
     /* pad			*/	NULL,
     /* pad			*/	NULL
     },
  { /* atPlot fields */
     /* draw			*/	AxisCoreDraw,
     /* draw_icon		*/	XtInheritDrawIcon,
     /* drawPS			*/	XtInheritDrawPS,
     /* draw_iconPS		*/	XtInheritDrawIconPS,
     /* recalc			*/	AxisCoreRecalc
  },
  { /* axisCore fields */
     /* range_proc		*/	AxisCoreRangeProc,
     /* calc_proc		*/	AxisCoreCalcProc
  }
};

WidgetClass atAxisCoreWidgetClass = (WidgetClass)&atAxisCoreClassRec;

/*****************************************************************
 * 
 * The core member procs
 */
static void AxisCoreClassPartInit(wc)
WidgetClass wc;
{
     AtAxisCoreWidgetClass super = 
	  (AtAxisCoreWidgetClass) wc->core_class.superclass;
     AtAxisCoreWidgetClass spc = (AtAxisCoreWidgetClass) wc;

#define CheckInherit(fld, inherit) \
     if (spc->axiscore_class.fld == inherit) \
	  spc->axiscore_class.fld = super->axiscore_class.fld;

     CheckInherit(range_proc, XtInheritRangeProc);
     CheckInherit(calc_proc, XtInheritCalcProc);
#undef CheckInherit
     *RCSid = *RCSid;		/* Keeps gcc quiet */
}

static void AxisCoreClassInit(wc)
WidgetClass wc;
{
     AtRegisterFontSizeConverter();
     AtRegisterFontStyleConverter();
}

/***************** Some helper functions for GC management ****************/
static void GetLabelGC P((AtAxisCoreWidget ac));
static void GetLabelGC(ac)
AtAxisCoreWidget ac;
{
     XGCValues v;

     v.foreground = ac->axiscore.label_color;
     v.background = ac->plot.background;
     ac->axiscore.label_gc = XtGetGC(XtParent((Widget)ac),
				     GCForeground | GCBackground, &v);
}

static void GetNumberGC P((AtAxisCoreWidget ac));
static void GetNumberGC(ac)
AtAxisCoreWidget ac;
{
     XGCValues v;

     v.foreground = ac->axiscore.number_color;
     v.background = ac->plot.background;
     ac->axiscore.number_gc = XtGetGC(XtParent((Widget)ac),
				     GCForeground | GCBackground, &v);
}

static void GetGridGC P((AtAxisCoreWidget ac));
static void GetGridGC(ac)
AtAxisCoreWidget ac;
{
     XGCValues v;

     v.foreground = ac->plot.foreground;
     v.line_style = LineOnOffDash;
     v.dashes = (char)2;
     
     ac->axiscore.grid_gc = 
	  XtGetGC(XtParent((Widget)ac), 
		  GCForeground | GCLineStyle | GCDashList, &v);
}

#define FreeLabelGC(w) XtReleaseGC((Widget)w, w->axiscore.label_gc)
#define FreeNumberGC(w) XtReleaseGC((Widget)w, w->axiscore.number_gc)
#define FreeGridGC(w) XtReleaseGC((Widget)w, w->axiscore.grid_gc)

/***************** Helper routines for the AtText management ****************/
static void GetLabelText P((AtAxisCoreWidget acw));
static void GetLabelText(acw)
AtAxisCoreWidget acw;
{
     AtAxisCorePart *ac = &acw->axiscore;
     
     if (ac->label && *ac->label) {
	  ac->label_text = 
	       AtTextCreate(ac->label, ac->ff, ac->label_size,
			    ac->label_style);
	  if (ac->vertical)
	       AtTextRotate(ac->label_text); 
     } else
	  ac->label_text = NULL;
}

#define FreeLabelText(a) AtTextDestroy(a->axiscore.label_text)

#define GetFF(a) \
     a->axiscore.ff = \
     AtFontFamilyGet(XtDisplay(XtParent((Widget)a)), \
		     a->axiscore.font_family);
#define FreeFF(a)  AtFontFamilyRelease(a->axiscore.ff);

#define CopyString(str) str = XtNewString(str)

/***************** The Initialize / destroy / setvalues functions *********/

static void AxisCoreInitialize(req, new)
AtAxisCoreWidget req, new;
{
     if (!new->axiscore.label) new->axiscore.label = "";
     CopyString(new->axiscore.label);
     if (!new->axiscore.font_family) {
	  /* Inherit it from parent */
	  Widget p = XtParent((Widget)new);
	  
	  XtVaGetValues(p, XtNfontFamily, &new->axiscore.font_family, NULL);
     }
     CopyString(new->axiscore.font_family);
     GetFF(new);
     GetLabelText(new);
     GetLabelGC(new);
     GetNumberGC(new);
     GetGridGC(new); 
     new->axiscore.scale = 
	  AtScaleCreate(new->axiscore.min, new->axiscore.max, 0, 1,
			AtTransformLINEAR);
     if (new->axiscore.min >= new->axiscore.max) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)), 
		       "Min is >= Max in AtAxisCore");
	  new->axiscore.min = 0.0;
	  new->axiscore.max = 1.0; 
     }
     new->axiscore.num_tics = new->axiscore.num_segments =
	  new->axiscore.num_subtics = 0;
     new->axiscore.x1 = new->axiscore.y1 = 0;
     new->axiscore.x2 = new->axiscore.x2 = new->axiscore.grid_length = 10;
     new->axiscore.max_num_width = new->axiscore.actual_num_width =
	  new->axiscore.axis_width = new->axiscore.label_line =
	       new->axiscore.tic_label_line = 0;
     
     new->axiscore.tic_values = NULL;
     new->axiscore.tic_label_string = NULL;
     new->axiscore.subtic_values = NULL;
     new->axiscore.segments = NULL;
     new->axiscore.tic_pos = NULL;
     new->axiscore.grid_segments = NULL;
     new->axiscore.tic_label_text = NULL;
     new->axiscore.numbers_changed = new->axiscore.position_changed = True;
}

static void AxisCoreDestroy(ac)
AtAxisCoreWidget ac;
{
     XtFree((char *)ac->axiscore.segments);
     XtFree(ac->axiscore.label); 
     XtFree(ac->axiscore.font_family); 
     FreeFF(ac);
     FreeLabelGC(ac);
     FreeLabelText(ac);
     FreeNumberGC(ac);
     FreeGridGC(ac); 
     AtScaleDestroy(ac->axiscore.scale); 
}

static Boolean AxisCoreSetValues(old, req, new)
AtAxisCoreWidget old, req, new;
{
#define Changed(fld)      (old->axiscore.fld != new->axiscore.fld)
     Boolean refresh = False;
     Boolean redraw = False;
     Boolean recalc = False;
     Boolean relayout = False;
     int old_w, new_w;
     AtAxisCorePart *ac = &new->axiscore;
     
     if (Changed(min) || Changed(max) || Changed(tic_interval)) {
	  recalc = True;
	  ac->numbers_changed = True;
     }
     if (new->axiscore.min >= new->axiscore.max) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)), 
		       "Min is >= Max in AtAxisCore");
	  new->axiscore.min = old->axiscore.min;
	  new->axiscore.max = old->axiscore.max; 
     }

     if (Changed(vertical)) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)), 
		       "Can't change XtNvertical for an axis");
	  ac->vertical = old->axiscore.vertical;
     }
     if (Changed(mirror)) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)), 
		       "Can't change XtNmirror for an axis");
	  ac->vertical = old->axiscore.vertical;
     }

     if (Changed(label)) {
	  XtFree(old->axiscore.label);
	  CopyString(ac->label);
	  redraw = True;
     }

     if (Changed(font_family)) {
	  XtFree(old->axiscore.font_family);
	  CopyString(ac->font_family);
	  FreeFF(new);
	  GetFF(new);
     }
     if (Changed(label) || Changed(font_family) ||
	 Changed(label_style) || Changed(label_size)) {
	  old_w = (ac->vertical ? AtTextWidth(ac->label_text) :
		   AtTextHeight(ac->label_text));
	  FreeLabelText(new);
	  GetLabelText(new);
	  new_w = (ac->vertical ? AtTextWidth(ac->label_text) :
		   AtTextHeight(ac->label_text));
	  redraw = True;
	  if (old_w != new_w) {
	       relayout = True;
	  }
     }
     if (Changed(label_color)) {
	  FreeLabelGC(new);
	  GetLabelGC(new);
	  refresh = True;
     }
     if (Changed(font_family) || Changed(number_size) ||
	 Changed(number_style)) {
	  redraw = True;
	  ReformatLabels(new, True); 
     }
     if (Changed(number_color)) {
	  FreeNumberGC(new);
	  GetNumberGC(new);
	  refresh = True;
     }
     if (new->plot.foreground != old->plot.foreground) {
	  FreeGridGC(new);
	  GetGridGC(new);
	  refresh = True;
     }
     if (Changed(draw_grid)) {
	  redraw = True;
     }
     
     if (Changed(tics_inside) || Changed(tics_outside) ||
	 Changed(subtic_length) || Changed(tic_length) ||
	 Changed(numbers_outside)) {
	  ac->position_changed = True;
	  relayout = True; 
	  redraw = True;
     }

     if (ac->tics_outside && (Changed(subtic_length) || Changed(tic_length))) {
	  relayout = True; 
     }

     if (recalc)
	  AtPlotterRecalcThisPlot((AtPlotWidget)new);
     if (relayout) {
	  AtPlotterLayoutRequired((AtPlotWidget)new);
	  CalcAxisWidth(new); 
     } 
     if (redraw)
	  AtPlotterRedrawRequired((AtPlotWidget)new);
     else if (refresh)
	  AtPlotterRefreshRequired((AtPlotWidget)new);
     return False;
}


static void CalcAxisWidth(acw)
AtAxisCoreWidget acw;
{
     AtAxisCorePart *ac = &acw->axiscore;
     int tl;
     
     tl =  ac->tics_outside ? Max(0, Max(ac->tic_length,
					 ac->subtic_length)) : 0;
     if (ac->vertical) {
	  ac->axis_width = MARGIN + tl +
	       (ac->label_text ? AtTextWidth(ac->label_text) + MARGIN : 0)
		    + (ac->draw_numbers && ac->numbers_outside ?
		       ac->max_num_width + MARGIN : 0);
     } else {
	  ac->axis_width = MARGIN + tl +
	       (ac->label_text ? AtTextHeight(ac->label_text) + MARGIN : 0)
		    + (ac->draw_numbers && ac->numbers_outside ?
		       ac->max_num_width + MARGIN : 0);
     }
}

/*****************************************************************
 *
 * The AtPlot member proceedure
 */
static void AxisCoreDraw(pw, dpy, win, region, refresh)
AtPlotWidget pw;
Display *dpy;
Window win;
Region region;
int refresh;
{
     AtAxisCorePart *ac = &(((AtAxisCoreWidget)pw)->axiscore);
     int i;
     
     if (ac->num_segments) {
	  if (region)
	       XSetRegion(dpy, pw->plot.gc, region); 
	  XDrawSegments(dpy, win, pw->plot.gc, ac->segments,
			ac->num_segments);
	  if (region)
	       XSetClipMask(dpy, pw->plot.gc, None); 
     }
     
     if (ac->label_text) {
	  if (ac->vertical)
	       AtTextDrawJustified(dpy, win, ac->label_gc,
				   ac->label_text,
				   AtTextJUSTIFY_CENTER,
				   AtTextJUSTIFY_CENTER, 
				   ac->label_line, ac->y2,
				   AtTextWidth(ac->label_text),
				   ac->y1 - ac->y2);
	  else
	       AtTextDrawJustified(dpy, win, ac->label_gc,
				   ac->label_text,
				   AtTextJUSTIFY_CENTER,
				   AtTextJUSTIFY_CENTER, 
				   ac->x1, ac->label_line,
				   ac->x2 - ac->x1,
				   -AtTextHeight(ac->label_text));
     }
     if (ac->draw_grid) {
	  if (region)
	       XSetRegion(dpy, ac->grid_gc, region); 
	  XDrawSegments(dpy, win, ac->grid_gc, ac->grid_segments,
			ac->num_tics);
	  if (region)
	       XSetClipMask(dpy, ac->grid_gc, None); 
     } 
     if (ac->draw_numbers) {
	  if (region)
	       XSetRegion(dpy, ac->number_gc, region); 
	  if (ac->vertical) {
	       for (i = 0; i < ac->num_tics; i++) {
		    int ypos;

		    if (!i) {
			 /* First is flush to Bottom */
			 ypos = ac->tic_pos[i];
		    } else if (i == ac->num_tics - 1) {
			 /* Last is flush to Top - which is low pixel no! */
			 ypos = ac->tic_pos[i] +
			      AtTextAscent(ac->tic_label_text[i]);
		    } else {
			 /* Rest centered on tic */
			 ypos = ac->tic_pos[i] +
			      AtTextHeight(ac->tic_label_text[i]) / 2;
		    } 
		    AtTextDraw(dpy, win, ac->number_gc,
			       ac->tic_label_text[i], ac->tic_label_line, ypos);
	       }
	  } else {
	       for (i = 0; i < ac->num_tics; i++) {
		    int xpos;

		    if (!i) {
			 /* First is flush to left */
			 xpos = ac->tic_pos[i];
		    } else if (i == ac->num_tics - 1) {
			 /* Last is flush to right */
			 xpos = ac->tic_pos[i] -
			      AtTextWidth(ac->tic_label_text[i]);
		    } else {
			 /* Rest centered on tic */
			 xpos = ac->tic_pos[i] -
			      AtTextWidth(ac->tic_label_text[i]) / 2;
		    } 
		    AtTextDraw(dpy, win, ac->number_gc,
			       ac->tic_label_text[i], xpos, ac->tic_label_line);
	       }
	  }
	  if (region)
	       XSetClipMask(dpy, ac->number_gc, None); 
     } 
}

/*****************************************************************
 * 
 * The recalc routine.
 *
 * The recalculation happens in two parts.
 *
 * This routine is the plot.recalc routine and is called by the
 * Plotter parent when recalc is desired.  It calls the subclass
 * recalc routine stored in axiscore.recalc_proc to set up the tics
 * and subtics array, then sets up the pixel values here.  Subclasses
 * should inherit this routine (from the AtPlot class) unless special
 * pixel calculation is required.
 * 
 */
static void AxisCoreRecalc(pw, xs, ys, from, to)
AtPlotWidget pw;
AtScale *xs, *ys;
int from, to; 
{
     AtAxisCalcProc fn;
     AtAxisCorePart *ac = &((AtAxisCoreWidget)pw)->axiscore;
     int i, tp, tm, stp, stm, old_num_tics = ac->num_tics;
     XSegment *sp;
     
     if (ac->numbers_changed) {
	  XtFree((char *)ac->tic_values);
	  XtFree((char *)ac->subtic_values);
	  for (i = 0; ac->tic_label_string && i < ac->num_tics; i++)
	       XtFree(ac->tic_label_string[i]);
	  XtFree((char *)ac->tic_label_string);
	  
	  ac->tic_label_string = NULL;
	  ac->tic_values = NULL;
	  ac->subtic_values = NULL;
	  ac->num_tics = ac->num_subtics = 0;
	  
	  /*
	   * Call the subclass calc function
	   */
	  fn = ((AtAxisCoreWidgetClass)
		XtClass((Widget)pw))->axiscore_class.calc_proc;
	  if (fn) {
	       fn((AtAxisCoreWidget)pw);
	  }
     }

     if (!(ac->numbers_changed || ac->position_changed)) {
	  /* Hmm. how come we got called if there is nothing to change?? */
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)pw)), 
		       "AtAxis Recalc called without a recalc pending?");
	  return;
     }
     
     /*
      * Make the pixel arrays from the stores position arrays
      *
      * First, the segments 
      */
     XtFree((char *)ac->segments);
     XtFree((char *)ac->grid_segments);
     XtFree((char *)ac->tic_pos);
     for (i = 0; ac->tic_label_text && i < old_num_tics; i++)
	  AtTextDestroy(ac->tic_label_text[i]);
     XtFree((char *)ac->tic_label_text);
     
     ac->num_segments = 1 + ac->num_tics + ac->num_subtics;
     ac->segments = sp =
	  (XSegment *)XtMalloc(sizeof(XSegment) * ac->num_segments);
     ac->tic_pos = (Dimension *)XtMalloc(ac->num_tics * sizeof(Dimension));
     ac->tic_label_text = (AtText **)XtMalloc(sizeof(AtText *) * ac->num_tics);
     
     /* first the actual axis */
     sp->x1 = ac->x1;
     sp->y1 = ac->y1;
     sp->x2 = ac->x2;
     sp->y2 = ac->y2;
     sp++;
     /* Now the tics */
     tp = tm = stp = stm = 0;
     if (ac->tics_inside && !ac->mirror || ac->tics_outside && ac->mirror) {
	  tp = ac->tic_length;
	  stp = ac->subtic_length;
     }
     if (ac->tics_inside && ac->mirror || ac->tics_outside && !ac->mirror) {
	  tm = ac->tic_length;
	  stm = ac->subtic_length;
     }
     for (i = 0; i < ac->num_tics; i++, sp++) {
	  int pos = AtScaleUserToPixel(ac->scale, ac->tic_values[i]);
	  ac->tic_pos[i] = pos;
	  assert((sp - ac->segments) < ac->num_segments); 
	  if (ac->vertical) {
	       sp->y1 = sp->y2 = pos;
	       sp->x1 = ac->x1 - tm;
	       sp->x2 = ac->x1 + tp;
	  } else {
	       sp->x1 = sp->x2 = pos;
	       /* remember +ve pixel is downward */
	       sp->y1 = ac->y1 + tm;
	       sp->y2 = ac->y1 - tp;
	  } 
     }
     /* Now the subtics */
     for (i = 0; i < ac->num_subtics; i++, sp++) {
	  int pos = AtScaleUserToPixel(ac->scale, ac->subtic_values[i]);
	  assert((sp - ac->segments) < ac->num_segments); 
	  if (ac->vertical) {
	       sp->y1 = sp->y2 = pos;
	       sp->x1 = ac->x1 - stm;
	       sp->x2 = ac->x1 + stp;
	  } else {
	       sp->x1 = sp->x2 = pos;
	       /* remember +ve pixel is downward */
	       sp->y1 = ac->y1 + stm;
	       sp->y2 = ac->y1 - stp;
	  } 
     }

     /* Now define the line against which tic labels are displayed */
     tp = ac->tics_outside && ac->numbers_outside || 
	  ac->tics_inside && !ac->numbers_outside ? 
	       Max(0, Max(ac->tic_length, ac->subtic_length)) : 0;
     if (ac->vertical) {
	  ac->tic_label_line = 
	       (!ac->mirror ^ !!ac->numbers_outside) ? 
		    ac->x2 + tp + MARGIN :
			 ac->x1 - tp - MARGIN - ac->max_num_width;
     } else {
	  /* Remember, towards bottom is DECREASING pixel address!! */
	  ac->tic_label_line = 
	       (!ac->mirror ^ !!ac->numbers_outside) ?
		    ac->y2 - MARGIN - tp :
			 ac->y1 + MARGIN + tp + ac->max_num_width;
     } 

     /*
      * Now, make the grid segments
      */
     ac->grid_segments = sp =
	  (XSegment *)XtMalloc(sizeof(XSegment) * ac->num_tics);
     if (ac->vertical) {
	  for (i = 0; i < ac->num_tics; i++, sp++) {
	       sp->y1 = sp->y2 = ac->tic_pos[i];
	       sp->x1 = ac->x1;
	       sp->x2 = ac->x1 + 
		    (ac->mirror ? -ac->grid_length : ac->grid_length);
	  }
     } else {
	  for (i = 0; i < ac->num_tics; i++, sp++) {
	       sp->x1 = sp->x2 = ac->tic_pos[i];
	       sp->y1 = ac->y1;
	       sp->y2 = ac->y1 -
		    (ac->mirror ? -ac->grid_length : ac->grid_length);
	  }
     }

     /*
      * Now convert the tic labels to AtText format
      */
     ReformatLabels((AtAxisCoreWidget)pw, False);
     
     /*
      * Now, layout the label etc
      */
     if (ac->label_text) {
	  tp = ac->tics_outside ? 
	       Max(0, Max(ac->tic_length, ac->subtic_length)) : 0;
	  tp += MARGIN + 
	       (ac->draw_numbers && ac->numbers_outside ? 
		ac->max_num_width + MARGIN : 0);
	  if (ac->vertical) {
	       ac->label_line = ac->mirror ? ac->x1 + tp : 
		    ac->x1 - tp - AtTextWidth(ac->label_text);
	  } else {
	       /* +y is towards bottom! */
	       ac->label_line = ac->mirror ? ac->y1 - tp : 
		    ac->y1 + tp + AtTextHeight(ac->label_text);
	  }
     }

     ac->numbers_changed = ac->position_changed = False;
}

static void ReformatLabels(acw, free_them)
AtAxisCoreWidget acw;
int free_them; 
{
     AtAxisCorePart *ac = &acw->axiscore;
     int wid, maxwid, i;

     maxwid = 0;
     for (i = 0; i < ac->num_tics; i++) {
	  if (free_them) AtTextDestroy(ac->tic_label_text[i]);
	  
	  ac->tic_label_text[i] =
	       AtTextCreate(ac->tic_label_string[i], ac->ff,
			    ac->number_size, ac->number_style);

	  wid = ac->vertical ? AtTextWidth(ac->tic_label_text[i]) :
	       AtTextHeight(ac->tic_label_text[i]);
	  maxwid = Max(maxwid, wid); 
     }
     if (maxwid != ac->actual_num_width && ac->draw_numbers &&
	 ac->numbers_outside && ac->max_num_width < maxwid) {
	  /* 
	   * The width of the numbers has changed, so request a rescale
	   * (so the max_num_width can be calculated). Its a pity,
	   * relayout is pretty much all that changes.
	   */
	  ac->numbers_changed = True;
	  AtPlotterRescaleRequired((AtPlotWidget)acw);
     }
     ac->actual_num_width = maxwid;
}


/*****************************************************************
 * 
 * The AxisCore member functions
 */

/*
 * The default range proc just accepts the answers, and calculates the
 * number_width based on the stored actual_number_width or (for
 * startup) by formatting max as a guess, and makes tic_interval
 * equal to the range.
 */
static void AxisCoreRangeProc(w, minp, maxp, tip, nwp)
AtAxisCoreWidget w;
double *minp, *maxp, *tip;
int *nwp;
{
     AtAxisCorePart *ac = &w->axiscore;
     AtText *tmp;
     char lbl[20];

     if (ac->actual_num_width) {
	  *nwp = ac->actual_num_width;
     } else { 
	  sprintf(lbl, "%.0g", *maxp);
	  tmp = AtTextCreate(lbl, ac->ff, ac->number_size,
			     ac->number_style);
     
	  /* Default Number width is with of max */
	  if (ac->vertical) {
	       *nwp = AtTextWidth(tmp);
	  } else
	       *nwp = AtTextHeight(tmp);
	  AtTextDestroy(tmp);
     }
     
     /* Default tic_interval is (max - min)! */
     *tip = *maxp - *minp;
}

/*
 * The default Calc proc just has two tics (at min and max) and three subtics.
 */
static void AxisCoreCalcProc(w)
AtAxisCoreWidget w;
{
     AtAxisCorePart *ac = &w->axiscore;
     char lbl[20];
     
     ac->tic_values = (double *)XtMalloc(sizeof(double) * 2);
     ac->tic_label_string = (String *)XtMalloc(sizeof (String) * 2); 
     ac->num_tics = 2;

     ac->tic_values[0] = ac->min;
     ac->tic_values[1] = ac->max;

     sprintf(lbl, "%.0f", ac->min);
     ac->tic_label_string[0] = XtNewString(lbl);
     sprintf(lbl, "%.0f", ac->max);
     ac->tic_label_string[1] = XtNewString(lbl); 
     
     ac->num_subtics = 3;
     ac->subtic_values = (double *)XtMalloc(sizeof(double) * 3);
     ac->subtic_values[0] = (ac->max + ac->min) / 2;
     ac->subtic_values[1] = (ac->max + ac->subtic_values[0]) / 2;
     ac->subtic_values[2] = (ac->min + ac->subtic_values[0]) / 2;
}


/*****************************************************************
 * 
 * The wrappers for the member functions that get called by the parent
 */

void AtAxisAskRange(acw, minp, maxp)
AtAxisCoreWidget acw;
double *minp, *maxp;
{
     AtAxisRangeProc fn;
     AtAxisRangeArgs ra;
     AtAxisCorePart *ac = &acw->axiscore;
     double new_ti, *tip = &new_ti;
     int old_width = ac->axis_width;
     int new_num_width = ac->actual_num_width, *widp = &new_num_width;
     Boolean rescale = False;
     
     ra.minp = minp;
     ra.maxp = maxp;
     ra.max_widthp = widp;
     ra.tic_intervalp = tip;

     /* 
      * First, ask the class method to suggest
      * min/max/tic interval/max label width 
      */ 
     fn = ((AtAxisCoreWidgetClass)
	   XtClass((Widget)acw))->axiscore_class.range_proc;

     if (fn) 
	  fn(acw, minp, maxp, tip, widp);

     /*
      * Then call the callbacks so they can suggest same
      */
     XtCallCallbackList((Widget)acw, ac->range_callback,
		     (XtPointer)&ra);

     /*
      * Decide if we need to recalculate our tics and subtics,
      * depending on whether or not min/max/ticinterval changed
      */
     if (ac->min != *minp || ac->max != *maxp) {
#ifdef TRACE
	  fprintf(stderr, "Axis (label %s) changed endpoints to %.1f,%.1f\n", 
		  ac->label, *minp, *maxp); 
#endif 
	  AtScaleRescale(ac->scale, *minp, *maxp);
	  rescale = True;
     }
     if (rescale || ac->tic_interval != *tip) {
	  ac->numbers_changed = True;
	  AtPlotterRecalcThisPlot((AtPlotWidget)acw);
     }

     /* Accept the values for min, max and ticinterval */
     ac->min = *minp;
     ac->max = *maxp;
     ac->tic_interval = *tip;
     ac->max_num_width = *widp;

     /*
      * Now calculate the actual axis width based on the max number
      * width and things like labels etc.
      */
     CalcAxisWidth(acw);
     if (ac->axis_width != old_width) {
#ifdef TRACE
	  fprintf(stderr, 
		  "AtAxisRange (label %s) changed max_width from %d to %d\n",
		  ac->label, old_width, ac->axis_width);
#endif
	  ac->position_changed = True;
	  AtPlotterLayoutRequired((AtPlotWidget)acw);
     }
}


/*****************************************************************
 *
 * This routine is called by the parent to set the location of the axis.
 * 
 */

Boolean AtAxisSetPosition(acw, x1, y1, x2, y2, grid_length)
AtAxisCoreWidget acw;
int x1, y1, x2, y2, grid_length;
{
     AtAxisCorePart *ac = &acw->axiscore;
     int old_len, len;
     Boolean len_changed = False;
     
     XtCheckSubclass((Widget)acw, atAxisCoreWidgetClass, 
		     "AtAxisSetPosition needs an AtAxisCoreWidget");
     
#define dif(var)     (ac->var != var)
     if (dif(x1) || dif(y1) || dif(x2) || dif(y2) || dif(grid_length)) {
	  AtPlotterRecalcThisPlot((AtPlotWidget)acw);
	  ac->position_changed = True;
#ifdef TRACE
	  fprintf(stderr, "Axis (label %s) changed position\n",
		  ac->label); 
#endif 
     }
#undef dif
     
     if (ac->vertical) {
	  old_len = ac->y1 - ac->y2;
	  len = y1 - y2;
     } else {
	  old_len = ac->x2 - ac->x1;
	  len = x2 - x1;
     }
     if (old_len < (len - (len >> 2)) || old_len > (len + (len >> 2))) {
	  /* Has changed length by 25%, so need to rethink tic_interval! */
	  len_changed = ac->numbers_changed = True;
#ifdef TRACE
	  fprintf(stderr, "Axis (label %s) changed in length\n",
		  ac->label); 
#endif 
     }
     
     ac->x1 = x1;
     ac->x2 = x2;
     ac->y1 = y1;
     ac->y2 = y2;
     ac->grid_length = grid_length; 
	 
     if (ac->vertical && x1 != x2) {
	  XtAppError(XtWidgetToApplicationContext((Widget)acw), 
		     "Vertical axis given non-vertical position");
     }
     if (!ac->vertical && y1 != y2) {
	  XtAppError(XtWidgetToApplicationContext((Widget)acw), 
		     "Horizontal axis given non-horizontal position");
     }

     /* Now change the scale */
     if (ac->vertical) {
	  AtScaleResize(ac->scale, y1, y2);
     } else {
	  AtScaleResize(ac->scale, x1, x2); 
     }

     return len_changed; 
}

/****************************************************************
 *
 * The simple member functions that have no virtual functions
 */

AtScale *AtAxisGetScale(acw)
AtAxisCoreWidget acw;
{
     if (!acw) return NULL;	/* Can happen with textplot classes */
     
     XtCheckSubclass((Widget)acw, atAxisCoreWidgetClass, 
		  "AtAxisGetScale needs an AtAxisCoreWidget");

     return acw->axiscore.scale; 
}

int AtAxisWidth(acw)
AtAxisCoreWidget acw;
{
     XtCheckSubclass((Widget)acw, atAxisCoreWidgetClass, 
		  "AtAxisWidth needs an AtAxisCoreWidget");
     
     return acw->axiscore.axis_width; 
}

void AtAxisGetBounds(acw, minp, maxp)
AtAxisCoreWidget acw;
double *minp, *maxp;
{
     *minp = acw->axiscore.min;
     *maxp = acw->axiscore.max; 
}

AtTransform AtAxisGetTransform(acw)
AtAxisCoreWidget acw;
{
     return AtScaleGetTransform(acw->axiscore.scale);
}

#define AtTextPSHeight(a) (AtTextPSAscent((a)) + AtTextPSDescent(a))
     
int AtAxisWidthPS(acw)
AtAxisCoreWidget acw;
{
     AtAxisCorePart *acp = &acw->axiscore;
     int i, max_num_width;
     
     if (!acp->vertical) {
	  return PS_MARGIN + (acp->tics_outside ? acp->tic_length : 0) +
	       (acp->draw_numbers && acp->numbers_outside ?
		AtTextPSHeight(acp->tic_label_text[0]) + PS_MARGIN : 0) +
		     (acp->label_text ?
		      AtTextPSHeight(acp->label_text) + PS_MARGIN : 0);
     }
     max_num_width = 0;
     if (acp->draw_numbers && acp->numbers_outside) {
	  for (i = 0; i < acp->num_tics; i++) {
	       int wid = AtTextPSWidth(acp->tic_label_text[i]);
	       max_num_width = Max(max_num_width, wid);
	  }
	  max_num_width += PS_MARGIN;
     }
     
     return PS_MARGIN + (acp->tics_outside ? acp->tic_length : 0) +
	  max_num_width +
	       (acp->label_text ?
		AtTextPSWidth(acp->label_text) + PS_MARGIN : 0);
}

void AtAxisDrawPS(acw, fp, sp, x1, y1, x2, y2, grid_len)
AtAxisCoreWidget acw;
FILE *fp;
AtScale *sp;
int x1, y1, x2, y2, grid_len;
{
     AtAxisCorePart *acp = &acw->axiscore;
     int tp, tm, stp, stm;
     int i;
     int tll, max_num_width;
     int x, y;

     fprintf(fp, "%%%% BeginObject: AtAxisCore\n");
     fprintf(fp, "GS\n");
     
     /* draw the line */
     fprintf(fp, "%d %d M %d %d L\n", x1, y1, x2, y2);    
    
     /* Now the tics */
     tp = tm = stp = stm = 0;
     if (acp->tics_inside && !acp->mirror || acp->tics_outside && acp->mirror) {
	  tp = acp->tic_length;
	  stp = acp->subtic_length;
     }
     if (acp->tics_inside && acp->mirror || acp->tics_outside && !acp->mirror) {
	  tm = acp->tic_length;
	  stm = acp->subtic_length;
     }
     for (i = 0; i < acp->num_tics; i++) {
	  int pos = AtScaleUserToPixel(sp, acp->tic_values[i]);
	  if (acp->vertical) {
	       fprintf(fp, "%d %d M %d %d L\n", x1 - tm, pos, x1 + tp, pos); 
	  } else {
	       fprintf(fp, "%d %d M %d %d L\n", pos, y1 - tm, pos, y1 + tp); 
	  } 
     }
     for (i = 0; i < acp->num_subtics; i++) {
	  int pos = AtScaleUserToPixel(sp, acp->subtic_values[i]);
	  if (acp->vertical) {
	       fprintf(fp, "%d %d M %d %d L\n", x1 - stm, pos, x1 + stp, pos); 
	  } else {
	       fprintf(fp, "%d %d M %d %d L\n", pos, y1 - stm, pos, y1 + stp); 
	  } 
     }
     fprintf(fp, "ST\n");

     /* Now the grid */
     if (acp->draw_grid) {
	  if (acp->mirror) grid_len = -grid_len;
	  
	  fprintf(fp, "0 setlinewidth\n[ ] 0 setdash\n");
	  for(i = 0; i<acp->num_tics; i++) {
	       int pos = 
		    AtScaleUserToPixel(sp, acp->tic_values[i]);
	       if (acp->vertical) 
		    fprintf(fp, "%d %d M %d %d L\n", x1, pos, 
			    x1 + grid_len, pos);
	       else
		    fprintf(fp, "%d %d M %d %d L\n", pos, y1, pos, 
			    y1 + grid_len);
	  }
	  fprintf(fp, "ST\n");
     }
     /*
      * Now the labels, if required
      */
     max_num_width = 0;
     if (acp->draw_numbers) {
	  int tl;
	  
	  if (acp->vertical) {
	       for (i = 0; i < acp->num_tics; i++) {
		    int wid = AtTextPSWidth(acp->tic_label_text[i]);
		    max_num_width = Max(wid, max_num_width); 
	       }
	  } else
	       max_num_width = AtTextPSHeight(acp->tic_label_text[0]);
	  tl = (acp->tics_outside && acp->numbers_outside || 
		acp->tics_inside && !acp->numbers_outside) ? 
		     acp->tic_length : 0;
	  if (acp->vertical) {
	       tll = (!acp->mirror ^ !!acp->numbers_outside) ? 
		    x2 + tl + PS_MARGIN : x1 - tl - PS_MARGIN - max_num_width;
	  } else {
	       tll = (!acp->mirror ^ !!acp->numbers_outside) ?
		    y1 + PS_MARGIN + tl : 
			 y2 - PS_MARGIN - tl - max_num_width ;
	  } 
	  for (i = 0; i < acp->num_tics; i++) {
	       int pos = AtScaleUserToPixel(sp, acp->tic_values[i]);
	       if (acp->vertical) {
		    x = tll;
		    if (!i) {
			 /* First is flush to bottom */
			 y = pos; 
		    } else if (i == acp->num_tics - 1) {
			 /* Last is flush to Top - which is low pixel no! */
			 y = pos - AtTextPSAscent(acp->tic_label_text[i]);
		    } else  {
			 /* Rest centered on tic */
			 y = pos - AtTextPSHeight(acp->tic_label_text[i]) / 2;
		    }
	       } else {
		    y = tll;
		    if (!i) {
			 /* First is flush to left */
			 x = pos;
		    } else if (i == acp->num_tics - 1) {
			 /* Last is flush to right */
			 x = pos - AtTextPSWidth(acp->tic_label_text[i]);
		    } else {
			 /* Rest centered on tic */
			 x = pos - AtTextPSWidth(acp->tic_label_text[i]) / 2;
		    } 
	       } 
	       AtTextPSDraw(fp, acp->tic_label_text[i], x, y);
	  }
     }
     /* Lastly, the label */
     if (acp->label_text) {
	  x = (x1 + x2) / 2;
	  y = (y1 + y2) / 2;
	  if (acp->vertical) {
	       if (acp->mirror) 
		    x += tp + PS_MARGIN + (acp->draw_numbers ? 
			       max_num_width + PS_MARGIN : 0);
	       else
		    x -= tm + PS_MARGIN + 
			 (acp->draw_numbers ? max_num_width + PS_MARGIN : 0) +
			      AtTextPSWidth(acp->label_text);
	       y -= AtTextPSAscent(acp->label_text) / 2 -
		    AtTextPSDescent(acp->label_text) / 2;
	  } else {
	       if (acp->mirror) {
		    y += tp + PS_MARGIN + AtTextPSDescent(acp->label_text);
		    if (acp->draw_numbers)
			 y += PS_MARGIN +
			      AtTextPSHeight(acp->tic_label_text[0]); 
	       }
	       else {
		    y -= tm + PS_MARGIN + AtTextPSAscent(acp->label_text);
		    if (acp->draw_numbers)
			 y -=  PS_MARGIN + 
			      AtTextPSHeight(acp->tic_label_text[0]);
	       }
	       x -= AtTextPSWidth(acp->label_text)/2;
	  }
	  AtTextPSDraw(fp, acp->label_text, x, y);
     }
    fprintf(fp, "GR\n%%%% EndObject AtAxisCore\n");
}

