/*
 * Generic Output Driver for X
 * X version 11
 *
 * This is the primary output driver used by the new X graph
 * to display output to the X server.  It has been factored
 * out of the original xgraph to allow mulitple hardcopy
 * output devices to share xgraph's capabilities.  Note:
 * xgraph is still heavily X oriented.  This is not intended
 * for porting to other window systems.
 */

#include "copyright.h"
#include "xgout.h"
#include "xgraph.h"

#define PADDING 	2
#define SPACE 		10
#define TICKLENGTH	5
#define MAXSEGS		1000

typedef struct x_state {
    Window win;			/* Primary window           */
};

void text_X();
void seg_X();
void dot_X();



void set_X(new_win, out_info)
Window new_win;			/* Newly created window */
xgOut *out_info;		/* Information to set   */
/* 
 * Sets some of the common parameters for the X output device.
 */
{
    struct x_state *new_state;

    out_info->dev_flags = ((depth > 3) ? D_COLOR : 0);
    out_info->area_w = out_info->area_h = 0; /* Set later */
    out_info->bdr_pad = PADDING;
    out_info->axis_pad = SPACE;
    out_info->legend_pad = 0;
    out_info->tick_len = TICKLENGTH;
#ifdef OLD
    out_info->axis_width =
      axisFont->max_bounds.rbearing - axisFont->max_bounds.lbearing;
#endif
    out_info->axis_width = XTextWidth(axisFont, "8", 1);
    out_info->axis_height =
      axisFont->max_bounds.ascent + axisFont->max_bounds.descent;
#ifdef OLD
    out_info->title_width =
      titleFont->max_bounds.rbearing - titleFont->max_bounds.lbearing;
#endif
    out_info->title_width = XTextWidth(titleFont, "8", 1);
    out_info->title_height =
      titleFont->max_bounds.ascent + titleFont->max_bounds.descent;
    out_info->max_segs = MAXSEGS;

    out_info->xg_text = text_X;
    out_info->xg_seg = seg_X;
    out_info->xg_dot = dot_X;
    out_info->xg_end = (void (*)()) 0;
    new_state = (struct x_state *) malloc(sizeof(struct x_state));
    new_state->win = new_win;
    out_info->user_state = (char *) new_state;
}



/*ARGSUSED*/
void init_X(user_state)
char *user_state;
/*
 * Initializes for an X drawing sequence.  Does nothing under X11.
 */
{
    /* Body left empty on purpose */
}

static GC textGC(t_win, t_font)
Window t_win;			/* Window for making GC */
XFontStruct *t_font;		/* Text font            */
/*
 * Sets the fields above in a global graphics context.  If
 * the graphics context does not exist,  it is created.
 */
{
    static GC text_gc = (GC) 0;
    XGCValues gcvals;
    unsigned long gcmask;

    gcvals.font = t_font->fid;
    gcmask = GCFont;
    if (text_gc == (GC) 0) {
	gcvals.foreground = normPixel;
	gcmask |= GCForeground;
	text_gc = XCreateGC(disp, t_win, gcmask, &gcvals);
    } else {
	XChangeGC(disp, text_gc, gcmask, &gcvals);
    }
    return text_gc;
}

static GC segGC(l_win, l_fg, l_style, l_width, l_chars, l_len)
Window l_win;			/* Window for making GC */
Pixel l_fg;			/* Foreground color */
int l_style;			/* Line style       */
int l_width;			/* Line width       */
char *l_chars;			/* Character spec   */
int l_len;			/* Length of spec   */
/*
 * Sets the fields above in a global graphics context.  If the
 * graphics context does not exist, it is created.
 */
{
    static GC segment_gc = (GC) 0;
    XGCValues gcvals;
    unsigned long gcmask;

    gcvals.foreground = l_fg;
    gcvals.line_style = l_style;
    gcvals.line_width = l_width;
    gcmask = GCForeground | GCLineStyle | GCLineWidth;
    if (segment_gc == (GC) 0) {
	segment_gc = XCreateGC(disp, l_win, gcmask, &gcvals);
    } else {
	XChangeGC(disp, segment_gc, gcmask, &gcvals);
    }
    if (l_len > 0) {
	XSetDashes(disp, segment_gc, 0, l_chars, l_len);
    }
    return segment_gc;
}

static GC dotGC(d_win, d_fg, d_clipmask, d_xorg, d_yorg)
Window d_win;			/* Window for making GC */
Pixel d_fg;			/* Foreground color */
Pixmap d_clipmask;		/* Clipmask         */
int d_xorg, d_yorg;		/* Clipmask origin  */
/*
 * Sets the fields above in a global graphics context.  If the
 * graphics context does not exist, it is created.
 */
{
    static GC dot_gc = (GC) 0;
    XGCValues gcvals;
    unsigned long gcmask;

    gcvals.foreground = d_fg;
    gcvals.clip_mask = d_clipmask;
    gcvals.clip_x_origin = d_xorg;
    gcvals.clip_y_origin = d_yorg;
    gcmask = GCForeground | GCClipMask | GCClipXOrigin | GCClipYOrigin;
    if (dot_gc == (GC) 0) {
	dot_gc = XCreateGC(disp, d_win, gcmask, &gcvals);
    } else {
	XChangeGC(disp, dot_gc, gcmask, &gcvals);
    }
    return dot_gc;
}



void text_X(user_state, x, y, text, just, style)
char *user_state;		/* Value set in xg_init   */
int x, y;			/* Text position (pixels) */
char *text;			/* Null terminated text   */
int just;			/* Justification (above)  */
int style;			/* Text style (above)     */
/*
 * This routine should draw text at the indicated position using
 * the indicated justification and style.  The justification refers
 * to the location of the point in reference to the text.  For example,
 * if just is T_LOWERLEFT,  (x,y) should be located at the lower left
 * edge of the text string.
 */
{
    struct x_state *st = (struct x_state *) user_state;
    XCharStruct bb;
    int rx, ry, len, height, width, dir;
    int ascent, descent;

    len = strlen(text);
    XTextExtents((style == T_TITLE) ? titleFont : axisFont, text, len,
		 &dir, &ascent, &descent, &bb);
    width = bb.rbearing - bb.lbearing;
    height = bb.ascent + bb.descent;
    
    switch (just) {
    case T_CENTER:
	rx = x - (width/2);
	ry = y - (height/2);
	break;
    case T_LEFT:
	rx = x;
	ry = y - (height/2);
	break;
    case T_UPPERLEFT:
	rx = x;
	ry = y;
	break;
    case T_TOP:
	rx = x - (width/2);
	ry = y;
	break;
    case T_UPPERRIGHT:
	rx = x - width;
	ry = y;
	break;
    case T_RIGHT:
	rx = x - width;
	ry = y - (height/2);
	break;
    case T_LOWERRIGHT:
	rx = x - width;
	ry = y - height;
	break;
    case T_BOTTOM:
	rx = x - (width/2);
	ry = y - height;
	break;
    case T_LOWERLEFT:
	rx = x;
	ry = y - height;
	break;
    }
    XDrawString(disp, st->win,
		textGC(st->win, ((style == T_TITLE) ? titleFont : axisFont)),
		rx, ry + bb.ascent, text, len);
}



void seg_X(user_state, ns, segs, width, style, lappr, color)
char *user_state;		/* Value set in xg_init */
int ns;				/* Number of segments   */
XSegment *segs;			/* X array of segments  */
int width;			/* Width of lines       */
int style;			/* See above            */
int lappr;			/* Line appearence      */
int color;			/* Line color (if any)  */
/*
 * This routine draws a number of line segments at the points
 * given in `seglist'.  Note that contiguous segments need not share
 * endpoints but often do.  All segments should be `width' devcoords wide
 * and drawn in style `style'.  If `style' is L_VAR,  the parameters
 * `color' and `lappr' should be used to draw the line.  Both
 * parameters vary from 0 to 7.  If the device is capable of
 * color,  `color' varies faster than `style'.  If the device 
 * has no color,  `style' will vary faster than `color' and
 * `color' can be safely ignored.  However,  if the
 * the device has more than 8 line appearences,  the two can
 * be combined to specify 64 line style variations.
 * Xgraph promises not to send more than the `max_segs' in the
 * xgOut structure passed back from xg_init().
 */
{
    struct x_state *st = (struct x_state *) user_state;
    GC gc;

    if (style == L_AXIS) {
	gc = segGC(st->win, normPixel, LineSolid, axisWidth, (char *) 0, 0);
    } else if (style == L_ZERO) {
	/* Set the color and line style */
	gc = segGC(st->win, zeroPixel, LineSolid, zeroWidth, (char *) 0, 0);
    } else {
	/* Color and line style vary */
	if (lappr == 0) {
	    gc = segGC(st->win, AllAttrs[color].pixelValue, LineSolid,
		       width, (char *) 0, 0);
	} else {
	    gc = segGC(st->win, AllAttrs[color].pixelValue, LineOnOffDash,
		       width, AllAttrs[lappr].lineStyle, AllAttrs[lappr].lineStyleLen);
	}
    }
    XDrawSegments(disp, st->win, gc, segs, ns);
}


#define LAST_CHECK

void dot_X(user_state, x, y, style, type, color)
char *user_state;		/* Value set in xg_init    */
int x, y;			/* Location in pixel units */
int style;			/* Dot style               */
int type;			/* Type of marker          */
int color;			/* Marker color (if any)   */
/*
 * This routine should draw a marker at location `x,y'.  If the
 * style is P_PIXEL,  the dot should be a single pixel.  If
 * the style is P_DOT,  the dot should be a reasonably large
 * dot.  If the style is P_MARK,  it should be a distinguished
 * mark which is specified by `type' (0-7).  If the output
 * device is capable of color,  the marker should be drawn in
 * `color' (0-7) which corresponds with the color for xg_line.
 */
{
    struct x_state *st = (struct x_state *) user_state;
    
    switch (style) {
    case P_PIXEL:
	XDrawPoint(disp, st->win,
		   dotGC(st->win, AllAttrs[color].pixelValue, (Pixmap) 0, 0, 0),
		   x, y);
	break;
    case P_DOT:
	XFillRectangle(disp, st->win,
		       dotGC(st->win, AllAttrs[color].pixelValue, dotMap,
			     (int) (x - (dot_w >> 1)),
			     (int) (y - (dot_h >> 1))),
		       (int) (x - (dot_w >> 1)), (int) (y - (dot_h >> 1)),
		       dot_w, dot_h);
	break;
    case P_MARK:
	XFillRectangle(disp, st->win,
		       dotGC(st->win, AllAttrs[color].pixelValue,
			     AllAttrs[type].markStyle,
			     (int) (x - mark_cx),
			     (int) (y - mark_cy)),
		       (int) (x - mark_cx), (int) (y - mark_cy),
		       mark_w, mark_h);
	break;
    }
}

