/*
 *      BarPlot.c
 *
 *      The AthenaTools Plotter Widget Set - Version 6.0
 *
 *      klin, Tue Jul  7 13:59:47 1992
 *      klin, Mon Jul 27 14:16:23 1992, patchlevel 2
 *                                      Draw() changed for drawing
 *                                      to a pixmap instead of a window.
 *      klin, Sat Aug 15 10:31:50 1992, patchlevel 4
 *                                      Minor changes in PS output.
 *                                      Changed <At/..> to <X11/At/..>.
 */
static char SCCSid[] = "@(#) Plotter V6.0  92/08/15  BarPlot.c";

/*

Copyright 1992 by University of Paderborn
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 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.

*/

/*
 *   AtBarPlot widget.  It is a subclass of the Splot.  Simple bar
 *   plots, nothing fancy.  Can do stacked bar plots with a bit of fudging.
 */

#include <X11/At/BarPlotP.h>

/*
 *   Forward declare all the private widgetclass routines
 */

static void BarPlotDraw P((AtPlotWidget, Display *, Drawable, Region, int));
static void BarPlotDrawIcon P((AtPlotWidget, Display *, Drawable,
				int, int, int, int, Region));
static void BarPlotDrawPS P((AtPlotWidget, FILE *, AtScale *, AtScale *));
static void BarPlotDrawIconPS P((AtPlotWidget, FILE *, int, int, int, int));
static void BarPlotRecalc P((AtPlotWidget, AtScale *, AtScale *, int, int));

static void BarPlotClassInit P((void));
static void BarPlotInitialize P((AtBarPlotWidget, AtBarPlotWidget));
static void BarPlotDestroy P((AtBarPlotWidget));
static Boolean BarPlotSetValues P((AtBarPlotWidget, AtBarPlotWidget,
				    AtBarPlotWidget));

static void BarPlotRecalcBB P((AtBarPlotWidget w, BoundingBox *));
static void BarPlotAttach P((AtSPlotWidget, BoundingBox *, int));

/* The resources */

static double one = 1.0, zero = 0.0;
#define off(field) XtOffsetOf(AtBarPlotRec, barplot.field)
static XtResource resources[] = {
  {
    XtNcellWidth, XtCCellWidth,
    XtRDouble, sizeof (double),
    off(cell_width), XtRDouble, (XtPointer) &one
  },
  {
    XtNcellOffset, XtCCellOffset,
    XtRDouble, sizeof (double),
    off(cell_offset), XtRDouble, (XtPointer) &zero
  },
  {
    XtNdoOutline, XtCDoOutline,
    XtRBoolean, sizeof (Boolean),
    off(do_outline), XtRImmediate, (XtPointer) False
  },
  {
    XtNdoFill, XtCDoFill,
    XtRBoolean, sizeof (Boolean),
    off(do_fill), XtRImmediate, (XtPointer) True
  },
  {
    XtNzeroMin, XtCZeroMin,
    XtRBoolean, sizeof (Boolean),
    off(zero_min), XtRImmediate, (XtPointer) True
  },
  {
    XtNfillColor, XtCForeground,
    XtRPixel, sizeof (Pixel),
    off(fill_color), XtRString, (XtPointer) XtDefaultForeground
  },
  {
    XtNshading, XtCShading,
    XtRShading, sizeof (AtShading),
    off(shading), XtRImmediate, (XtPointer) AtGRAY0
  },
  {
    XtNscreenShade, XtCScreenShade,
    XtRBoolean, sizeof (Boolean),
    off(screen_shade), XtRImmediate, (XtPointer) True
  }
};
#undef off

AtBarPlotClassRec atBarPlotClassRec = {
  { /* core fields */
    /* superclass               */      (WidgetClass) &atSPlotClassRec,
    /* class_name               */      "AtBarPlot",
    /* widget_size              */      sizeof(AtBarPlotRec),
    /* class_initialize         */      (XtProc) BarPlotClassInit,
    /* class_part_initialize    */      NULL,
    /* class_inited             */      FALSE,
    /* initialize               */      (XtInitProc) BarPlotInitialize,
    /* 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) BarPlotDestroy,
    /* pad                      */      NULL,
    /* pad                      */      NULL,
    /* set_values               */      (XtSetValuesFunc) BarPlotSetValues,
    /* 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                     */      BarPlotDraw,
    /* draw_icon                */      BarPlotDrawIcon,
    /* drawPS                   */      BarPlotDrawPS,
    /* draw_iconPS              */      BarPlotDrawIconPS,
    /* recalc                   */      BarPlotRecalc
  },
  { /* atSPlot fields */
    /* attach_data              */      BarPlotAttach,
  },
  { /* barPlot fields */
    /* empty                    */      0
  }
};

WidgetClass atBarPlotWidgetClass = (WidgetClass)&atBarPlotClassRec;

/*
 *   The init/setvalues procs
 */

static void BarPlotClassInit()
{
     AtRegisterShadingConverter();
     *SCCSid = *SCCSid;       /* Keep gcc quiet */
}

/*
 *   Set up the fillGC
 */

static void GetFillGC P((AtBarPlotWidget));
static void GetFillGC(bg)
AtBarPlotWidget bg;
{
     XGCValues v;
     int mask = GCForeground | GCBackground;

     v.foreground = bg->barplot.fill_color;
     v.background = bg->plot.background;

     if (bg->barplot.shading != AtGRAY0 && bg->barplot.screen_shade) {
	  v.tile = bg->barplot.shading_pixmap =
	       AtShadingGetPixmap(XtScreenOfObject((Widget)bg),
				  bg->barplot.shading,
				  bg->barplot.fill_color, bg->plot.background);
	  v.fill_style = FillTiled;
	  mask |= GCTile | GCFillStyle;
     } else {
	  mask |= GCFillStyle;
	  v.fill_style = FillSolid;
     }
     bg->barplot.fill_gc = XtGetGC(XtParent((Widget)bg), mask, &v);
}

static void FreeFillGC P((AtBarPlotWidget));
static void FreeFillGC(bg)
AtBarPlotWidget bg;
{
     if (bg->barplot.shading_pixmap != None)
	  AtShadingReleasePixmap(bg->barplot.shading_pixmap);
     bg->barplot.shading_pixmap = None;
     XtReleaseGC(XtParent((Widget)bg), bg->barplot.fill_gc);
}

/*
 *   Initialize
 */

static void BarPlotInitialize(req, new)
AtBarPlotWidget req, new;
{
     if (new->barplot.cell_width <= 0 || new->barplot.cell_width > 1) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellWidth resource must be between 0 and 1");
	  new->barplot.cell_width = 1.0;
     }
     if (new->barplot.cell_offset < -1.0 || new->barplot.cell_offset > 1.0) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellOffset resource must be between -1.0 and 1.0");
	  new->barplot.cell_offset = 0.0;
     }
     if (!new->barplot.do_fill && !new->barplot.do_outline) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot must have either doFill or doOutline");
	  new->barplot.do_fill = True;
     }
     new->barplot.shading_pixmap = None; /* Avoid bogus frees */
     GetFillGC(new);
     new->barplot.fill_rectangles = NULL;
}

/*
 *   SetValues
 */

static Boolean BarPlotSetValues(old, req, new)
AtBarPlotWidget old, req, new;
{
#define Changed(fld) (old->barplot.fld != new->barplot.fld)
     Boolean recalc = False;
     Boolean redraw = False;

     if (new->barplot.cell_width <= 0 || new->barplot.cell_width > 1) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellWidth resource must be between 0 and 1");
	  new->barplot.cell_width = 1.0;
     }
     if (new->barplot.cell_offset < -1.0 || new->barplot.cell_offset > 1.0) {
	  XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
		       "BarPlot cellOffset resource must be between -1.0 and 1.0");
	  new->barplot.cell_offset = 0.0;
     }

     if (Changed(cell_width) || Changed(cell_offset)) {
	  redraw = recalc = True;
     }
     if (Changed(do_fill) || Changed(do_outline)) {
	  if (!new->barplot.do_fill && !new->barplot.do_outline) {
	       XtAppWarning(XtWidgetToApplicationContext(XtParent((Widget)new)),
			    "BarPlot must have either doFill or doOutline");
	       new->barplot.do_fill = True;
	  }
	  redraw = True;
     }

     if (Changed(zero_min)) {
	  BoundingBox bb;
	  bb = new->barplot.saved_bb; /* No auto inititialization! */
	  BarPlotRecalcBB(new, &bb);
	  AtPlotterPlotDataChanged((AtPlotWidget)new, &bb, False);
     }

     if (Changed(fill_color) || Changed(shading) || Changed(screen_shade)) {
	  FreeFillGC(new);
	  GetFillGC(new);
	  redraw = True;
     }

     if (redraw)
	  AtPlotterRedrawRequired((AtPlotWidget)new);
     if (recalc)
	  AtPlotterRecalcThisPlot((AtPlotWidget)new);
     return False;
}
#undef Changed

/*
 *   Destroy
 */

static void BarPlotDestroy(bg)
AtBarPlotWidget bg;
{
     FreeFillGC(bg);
     XtFree((char *)bg->barplot.fill_rectangles);
}

/*
 *   Given the passed bar plot and bounding box, extract the saved
 *   bounding box and ajdust it according to the resources.
 */

static void BarPlotRecalcBB(w, bbp)
AtBarPlotWidget w;
BoundingBox *bbp;
{
     bbp->xmin += w->barplot.cell_offset;
     bbp->xmax += w->barplot.cell_width + w->barplot.cell_offset;
     if (w->barplot.zero_min) {
	  bbp->ymin = Min(bbp->ymin, 0);
	  bbp->ymax = Max(0, bbp->ymax);
     }
}

/*
 *   These routines are the ones called by the parent plot widget
 */

#define bp  (&((AtBarPlotWidget)self)->barplot)
#define bpw ((AtBarPlotWidget)self)
#define PIX ((XRectangle *)bpw->splot.pix)
/* NB PIX is not an lvalue (at least to some picky compilers) */

/*
 *   Needs to save the bounding box because the zeroMin resoucre changes
 *   how we interpret it.  Also neet to allocate memory.
 */

static void BarPlotAttach(self, bbp, extending)
AtSPlotWidget self;
BoundingBox *bbp;
int extending;
{
     if (extending) {
	  bpw->splot.pix = XtRealloc((char *)PIX,
				     bpw->splot.num_points * sizeof (XRectangle));
	  bp->fill_rectangles = (XRectangle *)
	       XtRealloc((char *)bp->fill_rectangles,
			 bpw->splot.num_points * sizeof (XRectangle));
	  bp->saved_bb.xmax =
	       Max(bp->saved_bb.xmax, bbp->xmax);
	  bp->saved_bb.ymax =
	       Max(bp->saved_bb.ymax, bbp->ymax);
	  bp->saved_bb.xmin =
	       Min(bp->saved_bb.xmin, bbp->xmin);
	  bp->saved_bb.ymin =
	       Min(bp->saved_bb.ymin, bbp->ymin);
     } else {
	  bp->saved_bb = *bbp;
	  bpw->splot.pix = XtMalloc(bpw->splot.num_points * sizeof (XRectangle));
	  XtFree((char *)bp->fill_rectangles);
	  bp->fill_rectangles = (XRectangle *)XtMalloc(bpw->splot.num_points *
						       sizeof (XRectangle));
     }
     BarPlotRecalcBB(bpw, bbp);
}

/*
 *   Draw the line clipped by the given region.
 */

static void BarPlotDraw(self, dpy, drw, region, refresh)
AtPlotWidget self;
Display *dpy;
Drawable drw;
Region region;
int refresh;
{
#ifdef TRACE
     fprintf(stderr, "BarPlotDraw, %d rectangles\n",
	     bpw->splot.num_points);
#endif
     if (bpw->splot.old_pix) {
	  if (bpw->plot.fast_update && refresh) {
	       /*
		* We are in fast update mode, doing a refresh and have old
		* pix stuff, so draw them to "erase" the old s first
		*/
	       /* Do in reverse order to below!! */
	       if (bp->do_outline)
		    XDrawRectangles(dpy, drw, bpw->plot.gc,
				    (XRectangle *)bpw->splot.old_pix,
				    bpw->splot.old_num_points);
	       if (bp->do_fill)
		    XFillRectangles(dpy, drw, bp->fill_gc,
				    (XRectangle *)bpw->splot.old_pix,
				    bpw->splot.old_num_points);
	  }
	  XtFree((char *)bpw->splot.old_pix);
	  bpw->splot.old_pix = NULL;
	  bpw->splot.old_num_points = 0;
     }

     /* Do the centre first, then outline */
     if (bp->do_fill) {
	  if (region) XSetRegion(dpy, bp->fill_gc, region);
	  XFillRectangles(dpy, drw, bp->fill_gc,
			  bp->do_outline ? bp->fill_rectangles : PIX,
			  bpw->splot.num_points);
	  if (region) XSetClipMask(dpy, bp->fill_gc, None);
     }
     if (bp->do_outline) {
	  if (region) XSetRegion(dpy, bpw->plot.gc, region);
	  XDrawRectangles(dpy, drw, bpw->plot.gc, PIX,
			  bpw->splot.num_points);
	  if (region) XSetClipMask(dpy, bpw->plot.gc, None);
     }
}

/*
 *   draw the "icon" in the given place.
 */

static void BarPlotDrawIcon(self, dpy, drw, x1, y1, width, height, region)
AtPlotWidget self;
Display *dpy;
Drawable drw;
int x1, y1, width, height;
Region region;
{
     if (bp->do_fill) {
	  if (region) XSetRegion(dpy, bp->fill_gc, region);
	  XFillRectangle(dpy, drw, bp->fill_gc, x1, y1, width, height);
	  if (region) XSetClipMask(dpy, bp->fill_gc, None);
     }

     if (bp->do_outline) {
	  if (region) XSetRegion(dpy, bpw->plot.gc, region);
	  XDrawRectangle(dpy, drw, bpw->plot.gc, x1, y1, width, height);
	  if (region) XSetClipMask(dpy, bpw->plot.gc, None);
     }
}

/*
 *   PostScript stuff
 */

static void BarPlotDrawPS(self, fp, xs, ys)
AtPlotWidget self;
FILE *fp;
AtScale *xs, *ys;
{
     int i, y0pix;
     char term[100];
     char *shade = AtShadingPS(bp->shading);

     fprintf(fp, "%%%%BeginObject: AtBarPlot\nGS\n");
     AtPlotPSLineStyle(fp, (AtPlotWidget)bpw);

     if (AtScaleGetLow(ys) < 0 && AtScaleGetHigh(ys) > 0) {
	  y0pix = AtScaleUserToPixel(ys, 0.0);
     } else if (AtScaleGetLow(ys) < 0) {
	  y0pix = AtScaleGetHighPix(ys);
     } else {
	  y0pix = AtScaleGetLowPix(ys);
     }

     if (bp->do_fill) {
	  if (bp->do_outline)
	       sprintf(term, "GS %s fill GR ST", shade);
	  else
	       sprintf(term, "%s fill", shade);
     } else strcpy(term, "ST");

     for (i = 0; i < bpw->splot.num_points; i++) {
	  int x0pix, x1pix, y1pix;

	  x0pix =
	       AtScaleUserToPixel(xs, i + bpw->splot.start + bp->cell_offset);
	  x1pix =
	       AtScaleUserToPixel(xs, i + bpw->splot.start + bp->cell_offset
				  + bp->cell_width);
	  y1pix = AtScaleUserToPixel(ys, AtSPlotGetValue((AtSPlotWidget)bpw, i));

	  fprintf(fp, "%d %d M %d %d L %d %d L %d %d L CP %s\n",
		  x0pix, y0pix, x0pix, y1pix, x1pix, y1pix, x1pix, y0pix, term);
     }
     fprintf(fp, "GR\n%%%%EndObject: AtBarPlot\n");
}

static void BarPlotDrawIconPS(self, fp, x1, y1, width, height)
AtPlotWidget self;
FILE *fp;
int x1, y1, width, height;
{
     char term[50], *shade = AtShadingPS(bp->shading);

     if (bp->do_fill) {
	  if (bp->do_outline)
	       sprintf(term, "GS %s fill GR ST", shade);
	  else
	       sprintf(term, "%s fill", shade);
     } else strcpy(term, "ST");


     fprintf(fp, "GS ");
     AtPlotPSLineStyle(fp, (AtPlotWidget)bpw);

     fprintf(fp, " %d %d M %d 0 RL 0 %d RL %d 0 RL CP %s GR\n",
	     x1, y1, width, -height, -width, term);
}

/*
 *   Recalc the data according to the passed x and y scales
 */

static void BarPlotRecalc(self, xs, ys, from, to)
AtPlotWidget self;
AtScale *xs, *ys;
int from, to;
{
     int i;
     int y0pix, xpix, hipix, wid;
     XRectangle *rp;

#ifdef TRACE
     fprintf(stderr, "BarPlotRecalc from %d to %d\n", from, to);
#endif
     if (from > to) {
	  from = 0;
	  to = bpw->splot.num_points - 1;
     }

     if (AtScaleGetLow(ys) < 0 && AtScaleGetHigh(ys) > 0) {
	  y0pix = AtScaleUserToPixel(ys, 0.0);
     } else if (AtScaleGetLow(ys) < 0) {
	  y0pix = AtScaleGetHighPix(ys);
     } else {
	  y0pix = AtScaleGetLowPix(ys);
     }

     rp = &bp->fill_rectangles[from];
     wid = Max(self->plot.line_width, 1);

     for (i = from; i <= to; i++, rp++) {
	  PIX[i].x = xpix =
	       AtScaleUserToPixel(xs, (double)(i + bpw->splot.start) +
				  bp->cell_offset);
	  PIX[i].y = hipix =
	       AtScaleUserToPixel(ys, AtSPlotGetValue((AtSPlotWidget)bpw, i));
	  PIX[i].width =
	       AtScaleUserToPixel(xs, bp->cell_width + bp->cell_offset +
				  (double)(i + bpw->splot.start)) - xpix;
	  PIX[i].height = hipix > y0pix ? hipix - y0pix : y0pix - hipix;
	  /* Now make the fill rectangles 1 pix smaller each edge */
	  rp->x = xpix + wid;
	  rp->y = hipix + wid;
	  rp->width = Max((int)PIX[i].width - wid, 0);
	  rp->height = Max((int)PIX[i].height - wid, 0);
     }
}
#undef bg
