 /******************************************************************\
  *                                                                *
  *  File Name:  xhpgl.c    part of hpgl to Xwindows program       *
  *                                                                *
  *                                                                *
  *  Original Author:  Randy L. Yach                               *
  *                                                                *
  *  Port to X11R4 and Enhancements: Andrew Gerber                 *
  *                                  gerber@solbourne.COM          *
  *  Port to X11R5 and SGI/IRIX:     Roger Brown, P.E.             *
  *                                  r.c.brown@ieee.org            *
  *  Functional description:                                       *
  *                                                                *
  *     This program will read a 7470a syntax HPGL file and        *
  *     display it on an Xwindow display.                          *
  *                                                                *
  \******************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>		/* MAXPATHLEN definition */

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#include "config.h"

extern Window	Select_Window();
extern FILE	*yyin;		/* File pointer for hpgl input file */

double		xMin,
		xMax,
		yMin,
		yMax,		/* user min/max coordinate boundaries */
		oxMin,
		oxMax,
		oyMin,
		oyMax;		/* original user min/max coordinate boundaries */

int		last_x,
		last_y,		/* last x and y coordinates for line drawing */
		x,
		y,		/* current x and y coordinates for line drawing */
		cur_pen,
		no_pen,
		line,		/* current pen color */
		pen_down,	/* pen status */
		absolute,	/* ploter status */
		span_count,	/* number of spans drawn */
		rectangle = 0,	/* flag for rectangle drawing */
		is_color,
		offscreen,
		last_offscreen,
		dont_scale = 0,	/* should we scale coordinates? Not done if zooming */
		x_org = 0,
		y_org = 0;	/* x and y origins */

unsigned int	minWidth,
		minHeight; 	/* size of X window for plotting */

short		font_offset;	/* distance from lower left to upper left of font */

char		file_name[MAXPATHLEN],	/* file name of hpgl input file */
		font_name[MAXPATHLEN];	/* font name of user supplied font */

Display		*dpy;

Window		main_window;	/* main window for plotting */

GC		gc;		/* default GC */

Font		text_font;	/* font info */

XFontStruct	*font_info;

XColor		pen[10];	/* all pen colors for plotting */

Cursor		wait_cursor,
		norm_cursor;

/******************************************************************\
 * prodedure usage()                                              *  
 ******************************************************************
 *    will pick a font based on the window size                   *
 \******************************************************************/
void usage(void)
{
	fprintf(stderr,"\nusage: xhpgl -fn [font] -cf [color_file] -geometry [geometry] plot_file\n");
	fprintf(stderr,"in window: [z] zoom [o] original [u] unzoom [r] redisplay [q] quit\n");
	exit(-1);
}

/**/
/******************************************************************\
 * prodedure select_font(x,y)                                     *
 ******************************************************************
 *    will pick a font based on the window size                   *
 \******************************************************************/
void get_text_font(int user_font)
{
	if (!user_font)
	{
		if ((minWidth < 350) || (minHeight < 350))
		{
			strcpy(font_name,"nil2");
		}
		else if ((minWidth < 700) || (minHeight < 700))
		{
			strcpy(font_name,"6x10");
		}
		else if ((minWidth < 800) || (minHeight < 800)) 
		{
			strcpy(font_name,"8x13");
		}
		else 
		{
			strcpy(font_name,"9x15");
		}
	}
  
	/* get the font and check if it is valid */
	text_font = XLoadFont(dpy, font_name);
	font_info = XQueryFont(dpy, text_font);
}


/******************************************************************\
 * procedure reverse                                              *
 ******************************************************************
 *    used to revers a string s                                   *
 \******************************************************************/
static void reverse(char *);
static void reverse(char *s)
{
	/* begin reverse */
	int	c,i,j;
  
	for (i = 0, j = strlen(s)-1; i < j; i++, j--) 
	{
		/* begin for string */
		c    = s[i];
		s[i] = s[j];
		s[j] = c;
	} /* end for string */
} /* end reverse */


/******************************************************************\
 * procedure itoa                                                 *
 ******************************************************************
 *    converts an integer(n) to characters in string(s)            *
 \******************************************************************/
static void itoa(char *, int);
static void itoa(char *s, int n) 
{
	/* begin itoa */
	int	i, sign;
  
	if ((sign = n) < 0)  /* record sign */
	{
		n = -n;
	}
	
	i = 0;
	do
	{	/* generate digits in reverse order */
		s[i++] = n % 10 + '0';  /* get next digit */
	} while ((n /= 10) > 0);  /* delete it */

	if (sign < 0)
	{
		s[i++] = '-';
	}
	s[i] = '\0';
	reverse(s);
} /* end itoa */


/******************************************************************\
 * procedure initialize_plotter                                   *
 ******************************************************************
 *    used to set up the default plotter conditions.              *
 \******************************************************************/
void initialize_plotter(void)
{
	x         =		/* set current x plotter coordinate */
	y      	  =		/* set current y plotter coordinate */
	last_x 	  =		/* set last x plotter coordinate */
	last_y 	  =		/* set last y plotter coordinate */
	pen_down  = 		/* put the pen in up position */
	offscreen = 0;

	absolute = 1;
 
	cur_pen=pen[0].pixel; /* select no pen or no color */
	no_pen=pen[0].pixel;  /* need a reference background color for text */
} /* end initialize_plotter */


/******************************************************************\
 * procedure draw_plot                                         *
 ******************************************************************
 *    used to plot the current picture                         *
 \******************************************************************/
static void draw_plot(void);
static void draw_plot(void)
{
	XEvent event;
  
	/* set the wait cursor */
	XDefineCursor(dpy, main_window, wait_cursor);
  
	/* if a redraw occures, open the file again
	   and call the yacc parser.  then flush and
	   discard all other events.  this keeps multiple
	   events from clogging the queue while a long
	   plot is painting.
	*/
	if ((yyin = fopen(file_name, "r")) == NULL)
	{ /* beign file error */
		fprintf(stderr, "Could not open file %s for read access.\n" ,file_name);
		exit(-1);
	} /* end file error */
  
	/* clear the display to get rid of any opbjects
	   that are not drawn to proper scale if the window
	   was resized
	*/
	XClearWindow(dpy, main_window);
	initialize_plotter();
	span_count = 0;

	yyparse();

	fclose(yyin);
  
	/* Clear any pending expose events or keyboard events */
	while(XCheckTypedEvent(dpy, Expose, &event)); 
	while(XCheckTypedEvent(dpy, KeyPress, &event)); 
  
	/* turn off the wait cursor */
	XUndefineCursor(dpy, main_window);
}


/******************************************************************\
 * procedure assign_color                                         *
 ******************************************************************
 *    used to assign a color to each plotter pen.                 *
 \******************************************************************/
void assign_color(int pen_num, char *color)
{
	if ((pen_num <=0) || (pen_num >= 9))
	{ /* begin invalid pen */
		fprintf(stderr,"pen numbers must be 1 to 8.\n");
		exit(-1);
	} /* end invalid pen */
	/* else */

	if (strcmp(color,"black")==0) 
	{ /* begin black */
		pen[pen_num].red=0; 
		pen[pen_num].green=0; pen[pen_num].blue=0;
	} /* end black */
	else if (strcmp(color,"red")==0) 
	{ /* begin red */
		pen[pen_num].red=65535; 
		pen[pen_num].green=0; pen[pen_num].blue=0;
	} /* end red */
	else if (strcmp(color,"gold")==0) 
	{ /* begin gold */
		pen[pen_num].red=61500;pen[pen_num].green=36750; 
		pen[pen_num].blue=25000;
	} /* end gold */
	else if (strcmp(color,"firebrick")==0) 
	{ /* begin firebrick */
		pen[pen_num].red=42600;pen[pen_num].green=10500; 
		pen[pen_num].blue=10500;
	} /* end firebrick */
	else if (strcmp(color,"maroon")==0) 
	{ /* begin maroon */
		pen[pen_num].red=42600; pen[pen_num].green=10500; 
		pen[pen_num].blue=32100;
	} /* end maroon */
	else if (strcmp(color,"orange")==0) 
	{ /* begin orange */
		pen[pen_num].red=61200; pen[pen_num].green=15000; 
		pen[pen_num].blue=15000;
	} /* end orange */
	else if (strcmp(color,"pink")==0) 
	{ /* begin pink */
		pen[pen_num].red=56400; pen[pen_num].green=42900; 
		pen[pen_num].blue=42900;
	} /* end pink */
	else if (strcmp(color,"turquoise")==0) 
	{ /* begin turquoise */
		pen[pen_num].red=51900; pen[pen_num].green=65535; 
		pen[pen_num].blue=65535;
	} /* end turquoise */
	else if (strcmp(color,"violet")==0) 
	{ /* begin violet */
		pen[pen_num].red=23700; pen[pen_num].green=14100; 
		pen[pen_num].blue=23700;
	} /* end violet */
	else if (strcmp(color,"green")==0) 
	{ /* begin green */
		pen[pen_num].red=0; 
		pen[pen_num].green=65535; pen[pen_num].blue=0;
	} /* end green */
	else if (strcmp(color,"blue")==0) 
	{ /* begin blue */
		pen[pen_num].red=0; 
		pen[pen_num].green=0; pen[pen_num].blue=65535;
	} /* end blue */
	else if (strcmp(color,"yellow")==0) 
	{ /* begin yellow */
		pen[pen_num].red=65535; 
		pen[pen_num].green=65535; pen[pen_num].blue=0;
	} /* end yellow */
	else if (strcmp(color,"cyan")==0) 
	{ /* begin cyan */
		pen[pen_num].red=0; 
		pen[pen_num].green=65535; pen[pen_num].blue=65535;
	} /* end cyan */
	else if (strcmp(color,"magenta")==0) 
	{ /* begin magenta */
		pen[pen_num].red=65535; 
		pen[pen_num].green=0; pen[pen_num].blue=65535;
	} /* end magenta */
	else if (strcmp(color,"white")==0) 
	{ /* begin white */
		pen[pen_num].red=65535; 
		pen[pen_num].green=65535; pen[pen_num].blue=65535;
	} /* end white */
	else 
	{ /* begin default white */
		fprintf(stderr,"Color %s not supported, using white.\n",color);
		pen[pen_num].red=65535; 
		pen[pen_num].green=65535; pen[pen_num].blue=65535;
	} /* begin default white */
} /* end assign_color */


main(int argc, char **argv)
{
	/* begin main */
  
	/* variable declerations */
	Pixmap pixmap,bpix; /* Used for frame and background of main window */
	XEvent event;       /* used for watching for keys in main window */
	XFontStruct *text_font_info; /* font information structure */
	char size_def[22];  /* is the default size used for creating the window */
	char temp[22];      /* temporary string holder */
	char user_color[10]; /* string for user color name */
	char *color_file_path; /* path to user color file */
	char color_file[MAXPATHLEN]; /* color file name */
	int user_font = 0;  /* need to know if the user entered a font */
  	int i; /* counter */
	FILE *color_file_pointer; /* color file pointer */
	Colormap default_colormap; /* default colormap */
	int default_screen; /* default screen */
	XCharStruct big_char;   
	XSetWindowAttributes attr;
	XSizeHints size_hints;
	int user_position = 0;     /* has user specified window size and location */
	int sync; /* has user specified synchronous mode */
	extern double xScale;
	extern double yScale;

	/* check to see if the user did not enter a file name and print
	   and error message */
	if (argc == 1)
	{
		usage();
	}
	
	/* set up default font_name and file_name */
	*font_name='\0';
	*file_name='\0';
  
	/* set up the default color file path and name */
	color_file_path = getenv("HOME");
	if (color_file_path != NULL)
	{
		strcpy(color_file,color_file_path);
		strcat(color_file,"/.hpcolors");
	}
	else
	{
		strcat(color_file,".hpcolors");
 	}   
  
	/* set up the default pixel size of the display window.  all plot
	   coordinates will be scaled to these coordinates
	*/
	minWidth=SIZEX;
	minHeight=SIZEY;
	strcpy(size_def,"=");
	itoa(temp,SIZEX);
	strcat(size_def,temp);
	strcat(size_def,"x");
	itoa(temp,SIZEY);
	strcat(size_def,temp);
	strcat(size_def,"+0+0");
  
	/* initialize all the pen colors to default value */
	pen[1].red=65535;	pen[1].green=0;		pen[1].blue=0;
	pen[2].red=0;		pen[2].green=65535;	pen[2].blue=0;
	pen[3].red=0;		pen[3].green=0;		pen[3].blue=65535;
	pen[4].red=0;		pen[4].green=65535;	pen[4].blue=65535;
	pen[5].red=65535;	pen[5].green=0;		pen[5].blue=65535;
	pen[6].red=65535;	pen[6].green=65535;	pen[6].blue=0;
	pen[7].red=65535;	pen[7].green=65535;	pen[7].blue=65535;
	pen[8].red=65535;	pen[8].green=65535;	pen[8].blue=65535;
  
	/* define the window black and white colors.  let the user
	   defint all the rest.  pen 0 is background black and pen 9
	   is frame white.  pens 1-8 are user definable
	*/
	pen[0].red=0;		pen[0].green=0;		pen[0].blue=0; /* background black */
	pen[9].red=65535;	pen[9].green=65535;	pen[9].blue=65535; /* white */
  
	/* parse the input arguments and get the file name and font name.
	   also get the geometry.
	   This program will use the last arg sent as the file to plot.
	*/
	for(i = argc - 1; i; i--)
	{ /* begin input arg parseing */
		if (!strcmp(argv[argc - i],"-fn"))
		{ /* begin arg fn */
			i--;
			strcpy(font_name,argv[argc - i]);
			user_font = 1;
		} /* end arg fn */
		if (!strcmp(argv[argc - i],"-cf"))
		{ /* begin color file */
			i--;
			strcpy(color_file,argv[argc - i]);
		} /* end color file */
		if (!strncmp(argv[argc - i], "-g", 2))
		{ /* begin geometry */
			i--;
			strcpy(size_def, argv[argc - i]);
			XParseGeometry(size_def, &x_org, &y_org, &minWidth, &minHeight);
			user_position = 1;
		}
		if (!strncmp(argv[argc - i], "-h", 2))
		{
			usage();
		}
		else
		{
			strcpy(file_name, argv[argc - i]);
		}
	} /* end input arg parseing */
  
	/* open the hpgl file and print error if not able to */
	if((yyin=fopen(file_name,"r"))==NULL)
	{ /* begin open file error */
		fprintf(stderr,"Could not open hpgl file -%s- for read.\n",file_name);
		exit(-1);
	} /* end open file error */
  
	/* open the color definition file and print message of not found */
	/* assign colors if file is found */
	if((color_file_pointer=fopen(color_file,"r"))==NULL)
	{ /* begin no color file */
		fprintf(stderr,"Could not open color file -%s- for read.\n",color_file);
		fprintf(stderr,"Using default internal values...\n");
	} /* end no color file */
	else
	{ /* begin assign colors */
		while(fscanf(color_file_pointer,"%d %s",&i,user_color) != EOF)
		{
			assign_color(i,user_color);
		}
		fclose(color_file_pointer);
	} /* begin assign colors */
  
	/* Set the origin and bounds of the coordinate system */
	xMin = XMIN;             
	yMin = YMIN;             
	xMax = XMIN + (SIZEX * MULT);
	yMax = YMIN + (SIZEY * MULT);
	xScale = minWidth / (xMax - xMin);
	yScale = minHeight / (yMax - yMin);

	/* connect to the Display as a client */
	dpy = XOpenDisplay(NULL);
	if (dpy == NULL)
	{
		fprintf(stderr,"Could not open Display\n");
		exit(-1);
	}
  
	/* Get the default screen */
	default_screen = XDefaultScreen(dpy);
  
	/* Test to see if we're on a color display */
	if (DisplayCells(dpy, default_screen) < 3)
	{
		is_color = 0;
	}
	else
    	{
		is_color = 1;

		/* Get the default colormap */
		/* allocate and create a hardware color for each of the pens */
		default_colormap = XDefaultColormap(dpy, default_screen);
		for (i = 0; i < 10; i++)
		if (!(XAllocColor(dpy, default_colormap, &pen[i])))
		{
			fprintf(stderr,"Ran out of colormap entries!");
			exit(-1);
		}
	}
  
  	/* Create the wait cursor */
	wait_cursor = XCreateFontCursor(dpy, XC_watch);
  	gc = XDefaultGC(dpy, default_screen);
	XSetForeground(dpy, gc, WhitePixel(dpy,default_screen));
	XSetBackground(dpy, gc, BlackPixel(dpy,default_screen));
  
	/* create the main window */
	main_window = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), x_org, y_org, minWidth,minHeight, 1, 0, BlackPixel(dpy, default_screen));
  
	/* create the XSizeHints structure */
	if (user_position)
	{
		size_hints.flags = USPosition | USSize;
		size_hints.x = x_org;
		size_hints.y = y_org;
		size_hints.width = minWidth;
		size_hints.height = minHeight;
	}
	else
	{
		size_hints.flags = NULL;
	}

	/* Set the window manager info for the window */
	XSetStandardProperties(dpy, main_window, argv[0], argv[0], NULL, argv, argc, &size_hints);

	/* select the proper font for the window size.  minWidth and minHeight
	  must be assigned to the current window size before this call is made,
	  or the font will be the wrong size
	*/
	get_text_font(user_font);
  
	/* check to make sure that the font selected exists on the hardware */
	if (text_font == 0)
	{/* begin get font error */
		fprintf(stderr,"Could not read font %s\n",font_name);
		exit(-1);
	}/* end get font error */
  
	/* get size of the font */
	text_font_info = XQueryFont(dpy, text_font);
	big_char = text_font_info->max_bounds;
	font_offset = big_char.ascent + big_char.descent;
  
	/* map the window */
	XMapWindow(dpy, main_window);
  
	/* set up the window to watch key events */
	XSelectInput(dpy,main_window,KeyPressMask | ExposureMask | StructureNotifyMask);
  
	/* Synchornize  - debug only */
	/*   XSynchronize(dpy, 1); */

	while (1) 
	{
		/* get the next pending event in the queue */
		XNextEvent(dpy, &event);
      
		/* switch on the event type */
		switch (event.type)
		{
			case Expose:
			{
				int	draw_it = 1;
			
				/* If a configureNotify event is on the queue, we don't want to process
			   	   this expose event.  Put the Configure Notify back on the queue; the extra
			   	   Expose events will be flushed by the draw_plot routine
				*/
				while (XCheckTypedEvent(dpy, ConfigureNotify, &event))
				{
					XPutBackEvent(dpy, &event);
					draw_it = 0;
				}
				if (draw_it)
				{
					draw_plot();
				}
			}
			break;

			case ConfigureNotify:
			{
				/* Window resize detected.  Rather than scale the drawing inside the window, we'll
				   adjust the coordinate system to display more or less of the drawing
				*/
		  		double xsiz, ysiz;
				int newWidth, newHeight;

				xsiz = xMax-xMin;
				ysiz = yMax-yMin;
  
				newWidth = event.xconfigure.width;
				newHeight = event.xconfigure.height;
  
				/* Nothing has changed.  So we do nothing! */
				if (newWidth == minWidth && newHeight == minHeight)
				{
					break;
				}
		  		/* else */
				xMax = (double)((xsiz/minWidth)*(newWidth - minWidth) + xMax);
				yMax = (double)((ysiz/minHeight)*(newHeight - minHeight) + yMax);
  
				minWidth = newWidth;
				minHeight = newHeight;
				draw_plot();
			}
			break;

			case KeyPress:
			{
				char key_char[1];
				XKeyEvent *key_event = (XKeyEvent *)&event;
				
  				XLookupString(key_event, key_char, 1, NULL, NULL);
				switch (key_char[0])
				{ 
					/* Quit */
					case 'Q':
					case 'q': 
					{
						XCloseDisplay(dpy);
						exit(0);
					}
					break;

					/* reset the Origin and bounds of the coordinate system */
					case 'O':
					case 'o':
					{
						xMin = XMIN;             
						yMin = YMIN;             
						xMax = XMIN + (SIZEX * MULT);
						yMax = YMIN + (SIZEY * MULT);
	
						dont_scale = 0;
						draw_plot();
					}
					break;

					/* Redraw */
					case 'R':
					case 'r':
					{
						draw_plot();
					}
					break;

					/* Zoom */
					case 'Z':
					case 'z':
					{
						short pboxX, pboxY, pboxW, pboxH;
						double xsiz, ysiz;
						double mult, multx, multy;
						
						if (Select_Window(dpy, main_window, &pboxX, &pboxY, &pboxW, &pboxH))
						{
							if (pboxX < 0) pboxX = 0;
							if (pboxY < 0) pboxY = 0;
							xsiz = xMax-xMin;
							ysiz = yMax-yMin;
	    
							/* Figure out which multiplier is larger and use that one */
							/* This prevents the zoomed image from getting out of proportion */
							multx = (double)(xsiz/pboxW);
							multy = (double)(ysiz/pboxH);
							mult = (double)(xsiz/minWidth);
							
							if (multx < multy) pboxH = pboxH * (multy/multx); 
							else pboxW = pboxW * (multx/multy); 
							
							xMin = (double)(((xsiz/minWidth)*(pboxX)) + xMin);
							yMin = (double)(((ysiz/minHeight)*(minHeight - (pboxY + pboxH))) + yMin);
							xMax = (double)(xMin + pboxW * mult);
							yMax = (double)(yMin + pboxH * mult);
							
							xScale = minWidth / (xMax - xMin);
							yScale = minHeight / (yMax - yMin);
							
							dont_scale = 1;
						}
						draw_plot();
					}
					break;

					/* Unzoom */
					case 'U':
					case 'u':
					{
						double xsiz, ysiz;
						
						xsiz = xMax-xMin;
						ysiz = yMax-yMin;
						
						xMax = xMax + .5 * xsiz;
						xMin = xMin - .5 * xsiz;
						yMax = yMax + .5 * ysiz;
						yMin = yMin - .5 * ysiz;
						
						xScale = minWidth / (xMax - xMin);
						yScale = minHeight / (yMax - yMin);
						
						dont_scale = 1;
						draw_plot();
					}
					break;
				
					default:
					break;
				} /* end key matching */
			} /* end key pressed */
			break;
			
			default:
			break;
		} /* end event switch */
	} /* end event handeling */
} /* end main */
