/*
 * 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 "params.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();


typedef struct attr_set {
    char lineStyle[MAXLS];
    int lineStyleLen;
    Pixel pixelValue;
    Pixmap markStyle;
} AttrSet;    

static AttrSet AllAttrs[MAXATTR];

static Pixmap dotMap = (Pixmap) 0;

/*
 * Marker bitmaps
 */

#include "bitmaps/dot.11"

#include "bitmaps/mark1.11"
#include "bitmaps/mark2.11"
#include "bitmaps/mark3.11"
#include "bitmaps/mark4.11"
#include "bitmaps/mark5.11"
#include "bitmaps/mark6.11"
#include "bitmaps/mark7.11"
#include "bitmaps/mark8.11"

/* Sizes exported for marker drawing */
static unsigned int dot_w = dot_width;
static unsigned int dot_h = dot_height;
static unsigned int mark_w = mark1_width;
static unsigned int mark_h = mark1_height;
static int mark_cx = mark1_x_hot;
static int mark_cy = mark1_y_hot;



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;
    XFontStruct *font;

    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;

    font = PM_FONT("LabelFont");
#ifdef OLD
    out_info->axis_width =
      font->max_bounds.rbearing - font->max_bounds.lbearing;
#endif
    out_info->axis_width = XTextWidth(font, "8", 1);
    out_info->axis_height =
      font->max_bounds.ascent + font->max_bounds.descent;

    font = PM_FONT("TitleFont");
#ifdef OLD
    out_info->title_width =
      font->max_bounds.rbearing - font->max_bounds.lbearing;
#endif
    out_info->title_width = XTextWidth(font, "8", 1);
    out_info->title_height =
      font->max_bounds.ascent + font->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;
}



static void init_once()
/*
 * Initializes AllAttrs.
 */
{
    Window temp_win;
    XSetWindowAttributes wattr;
    char name[1024];
    int idx;
    params style_val;

    /* Get attributes out parameters database */
    for (idx = 0;  idx < MAXATTR;  idx++) {
	(void) sprintf(name, "%d.Style", idx);
	(void) param_get(name, &style_val);
	AllAttrs[idx].lineStyleLen = style_val.stylev.len;
	(void) strncpy(AllAttrs[idx].lineStyle, style_val.stylev.dash_list,
		       style_val.stylev.len);
	(void) sprintf(name, "%d.Color", idx);
	AllAttrs[idx].pixelValue = PM_PIXEL(name);
    }

    /* Create a temporary window for representing depth */
    temp_win = XCreateWindow(disp, RootWindow(disp, screen),
			     0, 0, 10, 10, 0, depth, InputOutput,
			     vis, (unsigned long) 0, &wattr);

    /* Store bitmaps for dots and markers */
    dotMap = XCreateBitmapFromData(disp, temp_win, dot_bits, dot_w, dot_h);

    AllAttrs[0].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark1_bits, mark_w, mark_h);
    AllAttrs[1].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark2_bits, mark_w, mark_h);
    AllAttrs[2].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark3_bits, mark_w, mark_h);
    AllAttrs[3].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark4_bits, mark_w, mark_h);
    AllAttrs[4].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark5_bits, mark_w, mark_h);
    AllAttrs[5].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark6_bits, mark_w, mark_h);
    AllAttrs[6].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark7_bits, mark_w, mark_h);
    AllAttrs[7].markStyle = XCreateBitmapFromData(disp, temp_win,
						  mark8_bits, mark_w, mark_h);
    XDestroyWindow(disp, temp_win);
}

/*ARGSUSED*/
void init_X(user_state)
char *user_state;
/*
 * Initializes for an X drawing sequence.  Sets up drawing attributes
 * by reading values from the parameter database.
 */
{
    static int initialized = 0;

    if (!initialized) {
	init_once();
	initialized = 1;
    }
}

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 = PM_PIXEL("Foreground");
	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;
    XFontStruct *font;

    len = strlen(text);
    font = ((style == T_TITLE) ? PM_FONT("TitleFont") : PM_FONT("LabelFont"));
    XTextExtents(font, 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, font),
		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;
    param_style ps;
    GC gc;

    if (style == L_AXIS) {
	ps = PM_STYLE("GridStyle");
	if (ps.len < 2) {
	    gc = segGC(st->win, PM_PIXEL("Foreground"),
		       LineSolid, PM_INT("GridSize"), (char *) 0, 0);
	} else {
	    gc = segGC(st->win, PM_PIXEL("Foreground"),
		       LineOnOffDash, PM_INT("GridSize"),
		       ps.dash_list, ps.len);
	}
    } else if (style == L_ZERO) {
	/* Set the color and line style */
	ps = PM_STYLE("ZeroStyle");
	if (ps.len < 2) {
	    gc = segGC(st->win, PM_PIXEL("ZeroColor"),
		       LineSolid, PM_INT("ZeroWidth"), (char *) 0, 0);
	} else {
	    gc = segGC(st->win, PM_PIXEL("ZeroColor"),
		       LineOnOffDash, PM_INT("ZeroWidth"),
		       ps.dash_list, ps.len);
	}
    } 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;
    }
}

