/*
 * Copyright 1994 Loganville Technical Resources, Incorporated
 *
 *  standard GNU copyleft legalese mumbo-jumbo applies. (Really. I just don't
 *  want to copy all that stuff here, besides Johns got his below )
 *
 * 			Zmans Tachometer Widget 
 *
 * This is John Cwikla's hdial widget, (based on Youngs dial)slightly modified:
 *
 *   Changed to a 3/4 face tachometer (like the ORIGINAL!)
 *   Added a standard tach-like indicator arm.
 * 	 the arm is thicker for small tachs to make it easier to see      (AI!)
 *   The rotation of the arm can be either clockwise or counter clockwise
 *   The center hole now scales.  
 *	 depends on arm type and widget size
 *   The standard non-tach arm is triple wide (I know,its sort of bogus looking)
 *   The arc around the face is double wide by default   (ditto, but I like it)				 
 *   Changed maximum value default to 1000
 *   Changed default height/width to 100
 *   Altered orientation of actual tach within widget frame
 *
 *   Added a separate user specifiable label, placed at bottom of widget
 *   Can specify font and foreground color of this label
 *   
 *   Booleans for arm rotation, arm type, and double thickness
 *   TRUE means rotate clockwise, use tach arm, and use double thickness
 *
 *   Booleans for drawing the arc that forms the tach, the scale marks, 
 *   the arrow head (if !drawTachArm), the center hole and the number string
 *   all default to TRUE
 *
 *   Changed positioning of number label to be widget height based. so it is
 *   possible to move the label further from the center of the tach face
 *   by increasing  height/width ratio. (In fact it can be moved below face.) 
 *
 *   The User label is also height positioned.  It is at bottom - 4 pixels
 *   for height less than 150, else it is a bottom - user label font height.
 *
 *   Everything else is the same.    
 *
 *
 *
 * Copyright 1992 John L. Cwikla
 * 
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appears in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of John L. Cwikla or
 * University of Illinois not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written
 * prior permission.  John L. Cwikla and University of Illinois make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * John L. Cwikla and University of Illinois disclaim all warranties with
 * regard to this software, including all implied warranties of
 * merchantability and fitness, in no event shall John L. Cwikla or
 * University of Illinois 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.
 *
 * Author:
 * 	John L. Cwikla
 * 	Materials Research Laboratory Center for Computation
 * 	University Of Illinois at Urbana-Champaign
 *	104 S. Goodwin
 * 	Urbana, IL 61801
 * 
 * 	cwikla@uimrl7.mrl.uiuc.edu
 */

/* 
** Ztach.c 
*/


/* Default Translations:
 * <key>+: increment(1)
 * Shift<key>+: increment(100)
 * <key>-: decrement(1)
 * Shift<key>-: decrement(100)
 * <Btn1Down>: set()
 * <Btn1Motion>: set() drag()
*/

/* Resources:               	Type:      		Defaults:
 * XtNforeground          	: pixel    	: XtNDefaultForeground
 * XtNlabelForeground          	: pixel    	: XtNDefaultForeground
 * XtNminimum             	: int      	: 0
 * XtNmaximum             	: int      	: 1000
 * XtNvalue               	: int      	: 0
 * XtNfont                	: XFontStruct   : XtNDefaultFont
 * XtNmargin              	: int      	: 5
 * XtNimcrementCallback   	: callback 	: NULL
 * XtNdecrementCallbacki  	: callback 	: NULL
 * XtNvalueChangeCallback 	: callback 	: NULL
 * XtNulabelForeground          : pixel         :XtNDefaultForeground
 * XtNufont                     : XFontStruct   :XtNDefaultFont 
 * XtNulabel                    : String        :NULL
 * XtNdrawTachArm               : Boolean       :TRUE
 * XtNrotateCW                  : Boolean       :TRUE
 * XtNthickBits                 : Boolean       :TRUE          
 * XtNdrawArc                   : Boolean       :TRUE
 * XtNdrawMarkers               : Boolean       :TRUE 
 * XtNdrawAhead                 : Boolean       :TRUE 
 * XtNdrawHole                  : Boolean       :TRUE 
 * XtNdrawNumstring             : Boolean       :TRUE       
*/

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

#include <math.h>
#include <varargs.h>

/* #include <stdio.h>  */            	/* debug only */

#include "ZtachP.h"
#include "Ztach.h"


#define ZTACH_MINWIDTH 100           
#define ZTACH_MINHEIGHT 100	      	
#define LOCAL_PI 3.14159
#define RADIANS(a) (LOCAL_PI/180.0 * (double)(a))
#define DEGREES(a) (180.0 / LOCAL_PI * (a))
#define THETA 5.0 

/* Actions */
#if NeedFunctionProtoTypes
static void Increment(ZtachWidget _w, XEvent *_event, String *_argv, int *_argc);
static void Decrement(ZtachWidget _w, XEvent *_event, String *_argv, int *_argc);
static void Set(ZtachWidget _w, XEvent *_event, String *argv, int *_argc);
static void Drag(ZtachWidget _w, XMotionEvent *_xme);
#else
static void Increment();
static void Decrement();
static void Set();
static void Drag();
#endif

/* Widget Internals */ 
#if NeedFunctionProtoTypes
static void Initialize(ZtachWidget _request, ZtachWidget _new);
static void Resize(ZtachWidget _w);
static void Destroy(ZtachWidget _w);
static void Redisplay(ZtachWidget _w, XEvent *_event, Region _region);
static Boolean SetValues(ZtachWidget _current, ZtachWidget _request, 
							ZtachWidget _new);
#else
static void Initialize();
static void Resize();
static void Destroy();
static void Redisplay();
static Boolean SetValues();
#endif

/* Misc */
#if NeedFunctionProtoTypes
static void MoveArm(ZtachWidget _w);
static void ReValue(ZtachWidget _w);
static void DrawArrow(ZtachWidget _w, GC _gc);
static void DrawArm(ZtachWidget _w, GC _gc);
static void DrawLabel(ZtachWidget _w, GC _gc);
static void DrawULabel(ZtachWidget _w, GC _gc);
static void MyXtWarning();
#else
static void MoveArm();
static void ReValue();
static void DrawArrow();
static void DrawArm();
static void DrawLabel();
static void DrawULabel();
static void MyXtWarning();
#endif



static char ZtachTranslations[] =
"Shift<Key>+:   increment(100)\n\
 <Key>+:	increment(1)\n\
 Shift<Key>-:   decrement(100)\n\
 <Key>-:	decrement(1)\n\
 <Btn1Down>:    set()\n\
 <Btn1Motion>:  set() drag()";

static XtActionsRec ZtachActions[] = 
{
  {"increment", Increment},
  {"decrement", Decrement},
  {"set", Set},
  {"drag", Drag}
};

#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#define TheOffset(field) XtOffset(ZtachWidget, ztach.field)



static XtResource ZtachResources[] = 
{
  {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
    TheOffset(foreground), XtRString, (caddr_t)XtDefaultForeground},
  {XtNlabelForeground, XtCLabelForeground, XtRPixel, sizeof(Pixel),
    TheOffset(labelForeground), XtRString, (caddr_t)XtDefaultForeground},
  {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
    TheOffset(font), XtRString, (caddr_t)XtDefaultFont},
  {XtNminimum, XtCMinimum, XtRInt, sizeof(int),
    TheOffset(minimum), XtRImmediate, (caddr_t)0},
  {XtNmaximum, XtCMaximum, XtRInt, sizeof(int),
    TheOffset(maximum), XtRImmediate, (caddr_t)1000},    
  {XtNvalue, XtCValue, XtRInt, sizeof(int),
    TheOffset(value), XtRImmediate, (caddr_t)0},
  {XtNincrementCallback, XtCCallback, XtRCallback, sizeof(caddr_t),
    TheOffset(incrementCallback), XtRCallback, (caddr_t)NULL},
  {XtNdecrementCallback, XtCCallback, XtRCallback, sizeof(caddr_t),
    TheOffset(decrementCallback), XtRCallback, (caddr_t)NULL},
  {XtNvalueChangeCallback, XtCCallback, XtRCallback, sizeof(caddr_t),
    TheOffset(valueChangeCallback), XtRCallback, (caddr_t)NULL},
  {XtNmargin, XtCMargin, XtRInt, sizeof(int),
    TheOffset(margin), XtRImmediate, (caddr_t)5},
  {XtNulabelForeground, XtCULabelForeground, XtRPixel, sizeof(Pixel),
    TheOffset(UlabelForeground), XtRString, (caddr_t)XtDefaultForeground},
  {XtNufont, XtCUFont, XtRFontStruct, sizeof(XFontStruct *),
    TheOffset(ufont), XtRString, (caddr_t)XtDefaultFont},
  {XtNulabel, XtCULabel, XtRString, sizeof(String),
    TheOffset(Ulabel), XtRString, ""},
  {XtNdrawTachArm, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawTachArm), XtRString, "TRUE"},
  {XtNrotateCW, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(rotateCW), XtRString, "TRUE"}, 
  {XtNthickBits, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(thickBits), XtRString, "TRUE"},
  {XtNdrawArc, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawArc), XtRString, "TRUE"},
  {XtNdrawMarkers, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawMarkers), XtRString, "TRUE"}, 
  {XtNdrawAhead, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawAhead), XtRString, "TRUE"}, 
  {XtNdrawHole, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawHole), XtRString, "TRUE"},
  {XtNdrawNumstring, XtCBoolean, XtRBoolean, sizeof(Boolean),
    TheOffset(drawNumstring), XtRString, "TRUE"}, 
};

#undef TheOffset



ZtachClassRec ztachClassRec = 
{
  {        /* CoreClassPart */
    (WidgetClass)&widgetClassRec,  /* superclass */
    "Ztach",                       /* class_name */
    sizeof(ZtachRec),              /* widget_size */
    NULL,                          /* class_initialize */
    NULL,                          /* class_part_initialize */
    FALSE,                         /* class_init */
    Initialize,                    /* initialize */
    NULL,                          /* initialize_hook */
    XtInheritRealize,              /* realize */
    ZtachActions,                  /* actions */
    XtNumber(ZtachActions),        /* num_actions */
    ZtachResources,                /* resources */
    XtNumber(ZtachResources),      /* num_resources */
    NULLQUARK,                     /* xrm_class */
    TRUE,                          /* compress_motion */
    TRUE,                          /* compress_exposure */
    TRUE,                          /* compress_enterleave */
    TRUE,                          /* visible_intress */
    Destroy,                       /* destroy */
    Resize,                        /* resize */
    Redisplay,                     /* expose */
    SetValues,                     /* set_values */
    NULL,                          /* set_values_hook */
    XtInheritSetValuesAlmost,      /* set_values_almost */
    NULL,                          /* get_values_hook */
    NULL,                          /* accept_focus */
    XtVersion,                     /* version */
    NULL,                          /* callback_private */
    ZtachTranslations,             /* tm_translations */
    NULL,
    NULL,
    NULL,
  },
  { 
    0 /* empty */
  }
};

WidgetClass ztachWidgetClass = (WidgetClass) &ztachClassRec;



#define ZTACH _new->ztach
#define CORE _new->core

static void Initialize(_request, _new)
ZtachWidget _request;
ZtachWidget _new;
{
  XGCValues gcValues;
  Display *display;

  display = XtDisplay(_new);

	if (ZTACH.ufont == (XFontStruct *)NULL)
  	   {
    	    if ((ZTACH.ufont = XLoadQueryFont(display, "fixed")) == NULL) 
      		if ((ZTACH.ufont = XLoadQueryFont(display, "9x15")) == NULL)
        		MyXtWarning("ZtachWidget: uFonts %s and %s not found.", 
        							"fixed", "9x15");
  	   }
	ZTACH.UlabelHeight = ZTACH.ufont->ascent + ZTACH.ufont->descent;
  	ZTACH.UlabelWidth = XTextWidth(ZTACH.ufont, ZTACH.Ulabel, 
  							strlen(ZTACH.Ulabel));
	gcValues.foreground = ZTACH.UlabelForeground;
  	gcValues.font = ZTACH.ufont->fid;
  	ZTACH.UlabelGC = XtGetGC((Widget)_new, GCFont | GCForeground | 
  						GCBackground, &gcValues);


  if (ZTACH.font == (XFontStruct *)NULL)
  {
    if ((ZTACH.font = XLoadQueryFont(display, "fixed")) == NULL) 
      if ((ZTACH.font = XLoadQueryFont(display, "9x15")) == NULL)
        MyXtWarning("ZtachWidget: Fonts %s and %s not found.", "fixed", "9x15");
  }

  if (ZTACH.minimum >= ZTACH.maximum)
  {
    MyXtWarning("ZtachWidget: Maximum %d is less than minimum (%d).  Maximum set to %d.", 
      ZTACH.maximum, ZTACH.maximum, ZTACH.minimum+1);
    ZTACH.maximum = ZTACH.minimum+1;
  }

  if (ZTACH.value < ZTACH.minimum)
  {
    MyXtWarning("ZtachWidget: Value %d is less than minimum (%d).  Value set to %d.",
      ZTACH.value, ZTACH.minimum, ZTACH.minimum);
    ZTACH.value = ZTACH.minimum;
  }

  if (ZTACH.value > ZTACH.maximum)
  {
    MyXtWarning("ZtachWidget: Value %d is more than maximum (%d).  Value set to %d.",
      ZTACH.value, ZTACH.maximum, ZTACH.maximum);
    ZTACH.value = ZTACH.maximum;
  }

  sprintf(ZTACH.label, "%d", ZTACH.value);
  ZTACH.labelHeight = ZTACH.font->ascent + ZTACH.font->descent;
  ZTACH.labelWidth = XTextWidth(ZTACH.font, ZTACH.label, strlen(ZTACH.label));
 
  if (_request->core.width == 0)
    CORE.width = ZTACH_MINWIDTH + 2 * ZTACH.margin;
  if (_request->core.height == 0)
  							/* FOOL WITH MARGINS */
  
    	CORE.height = ZTACH_MINHEIGHT + 2 * ZTACH.margin;

  gcValues.foreground = ZTACH.foreground;
  gcValues.background = CORE.background_pixel;
  gcValues.line_width = 0;
  ZTACH.gc = XtGetGC((Widget)_new, GCForeground | GCBackground | GCLineWidth, &gcValues);
  gcValues.foreground = ZTACH.labelForeground;
  gcValues.font = ZTACH.font->fid;
  ZTACH.labelGC = XtGetGC((Widget)_new, GCFont | GCForeground | GCBackground, &gcValues);
  gcValues.foreground = CORE.background_pixel;
  gcValues.background = CORE.background_pixel;
  gcValues.line_width = 0;
  ZTACH.eraseGC = XtGetGC((Widget)_new, GCFont | GCForeground | GCBackground | GCLineWidth, &gcValues);

  Resize(_new);
}
#undef ZTACH
#undef CORE



#define ZTACH _w->ztach
#define CORE _w->core

static void MoveArm(_w)
ZtachWidget _w;
{
  double angle;
  double rangle;
 
  angle = (ZTACH.value - ZTACH.minimum)/(double)(ZTACH.maximum - ZTACH.minimum)
  					* 270.0; /* mo betta blues */

  /* Correct for a 270 degree (3pi/4 radian) dial.  This assumes the dial */
  /* motion is from left to right on a screen with 0,0 at top left.  The  */
  /* dial zero point is -45 degreess with respect to 0 being a horiz line */
				     /* which is how THIS functin sees it */
	angle -= 45;

	if (ZTACH.rotateCW == FALSE)
		angle = 180 - angle;		/* reverse the phase */

  rangle = RADIANS(angle);

  ZTACH.top.x = ZTACH.center.x - (int)(cos(rangle)*ZTACH.length*7.0/8.0);
  ZTACH.top.y = ZTACH.center.y - (int)(sin(rangle)*ZTACH.length*7.0/8.0);
}



static void DrawArrow(_w, _gc)
ZtachWidget _w; 
GC _gc;
{
  double angle;
  double rangle;
  XPoint tri[3];
  angle = (ZTACH.value - ZTACH.minimum)/(double)(ZTACH.maximum - ZTACH.minimum) 
  							* 270.0;
	angle -= 45;     /* as above */

	if (ZTACH.rotateCW == FALSE)
		angle = 180 - angle;		/* reverse the phase */

  angle+=THETA;
  rangle = RADIANS(angle);
  tri[1].x = ZTACH.center.x - (int)(cos(rangle)*ZTACH.length*6.0/8.0);
  tri[1].y = ZTACH.center.y - (int)(sin(rangle)*ZTACH.length*6.0/8.0);
  angle -= 2.0*THETA;
  rangle = RADIANS(angle);
  tri[2].x =  ZTACH.center.x - (int)(cos(rangle)*ZTACH.length*6.0/8.0);
  tri[2].y =  ZTACH.center.y - (int)(sin(rangle)*ZTACH.length*6.0/8.0);
  tri[0].x = ZTACH.top.x;
  tri[0].y = ZTACH.top.y;
  XFillPolygon(XtDisplay(_w), XtWindow(_w), _gc, tri, 3, Convex, CoordModeOrigin);
}



static void Resize(_w)
ZtachWidget _w;
{
  double width, height;
  int offset;

  width = CORE.width - 2.0 * ZTACH.margin;
							/* FOOL WITH MARGINS */
											
  height = CORE.height - 2.0 * ZTACH.margin;

  ZTACH.length = MIN(width/2.0, height);

  ZTACH.center.x = CORE.width/2;

							/* FOOL WITH MARGINS */
  ZTACH.center.y = CORE.height / 2 + (height / 20); 
    
  
  ZTACH.labelPos.x = ZTACH.center.x - (ZTACH.labelWidth / 2);
  
  							/* FOOL WITH LABEL POS */
  
  ZTACH.labelPos.y = ZTACH.center.y + (int)(height / 4);


	ZTACH.UlabelPos.x = ZTACH.center.x - (ZTACH.UlabelWidth / 2);
	if (CORE.height > 150)
		ZTACH.UlabelPos.y = CORE.height - ZTACH.UlabelHeight;	
	else
		ZTACH.UlabelPos.y = CORE.height - 4;	
  MoveArm(_w);
}

#undef ZTACH
#undef CORE



/*
*
*	Big trouble with XFreeFont, which is supposed to free storage
*	for a font structure (XFontStruct) and unload the font IF NO OTHER
*	client has loaded it.  Unfortunately this does not work.  All clients
*	(or at least all widgets within a client) appear to be getting a 
*	common pointer to the font struct.  Using XFreeFont fails (causing
*	the clients cpu usage to go high, locking out not only this client
*	but also degrading the rest of your system!) in a single widget when
*       the user label font is free'd if it is the same as the number string
*       font.  A simple work-around is to check if it is the same pointer
*       as the number string pointer, and if it is, nix the free.  The problem
*	is that this check will not work for multiple widgets.  Xzet uses two
*       tachs in each pop-up process widget and the second tach will lock when
*       it tries to free the number string font.  Dumping the struct shows 
*       that the first four bytes are always zero before XFreeFont is called,
*       but I can't see counting on that as a check for attempting the free.
*       
*	Since the pointers are the same, it would appear the only safe thing
*	to do is just not do the free.  I have added a define here in case 
*	XFreeFont is fixed (if that is even where the problem is).  This bug
*       never surfaces if you don't do a destroy widget or if you don't 
*       destroy more than one with the same label fonts.
*
*	I have verified (with Xzet) that even multiple pop-up process widgets
*	(four tachs, 8 label fonts) utilize the same XFontStruct pointer, so
*       it appears the memory hit will be small or none.
*
*	If anyone knows a safe/better way to accomplish freeing the font 
*       resources used by this widget please let me know. I don't like the
*	idea of the widget wasting memory (and my knowing about it).
*/
static void Destroy(_w)
ZtachWidget _w;
{
	char 	*XFS_ptr;
	int	i;
	
/*  printf("\nZtachWidget: Destroy");
  fflush(stdout);			*/
  XtReleaseGC((Widget)_w, _w->ztach.gc);
  XtReleaseGC((Widget)_w, _w->ztach.labelGC);
  XtReleaseGC((Widget)_w, _w->ztach.eraseGC);
/*  printf("...font (>%lx)", _w->ztach.font);
  fflush(stdout);
  	XFS_ptr = (char *)_w->ztach.font;
  	for (i=0; i< sizeof(XFontStruct); i++)
  	       {
  	       	if ( (i % 16) == 0) 	
  			printf("\n%02x - ",i);
  		printf("%02x ", (*XFS_ptr++ & 0x00ff) );
  	       }
  	printf("\n");       			 
  	fflush(stdout);			*/

#ifdef GOOD_XFREEFONT  
  XFreeFont(XtDisplay(_w),_w->ztach.font);
#endif  

/*  printf("...uGC");
  fflush(stdout);			*/
  XtReleaseGC((Widget)_w, _w->ztach.UlabelGC);
/*  printf("...ufont (>%lx)", _w->ztach.ufont);
  fflush(stdout);
  	XFS_ptr = (char *)_w->ztach.ufont;
  	for (i=0; i< sizeof(XFontStruct); i++)
  	       {
  	       	if ( (i % 16) == 0) 	
  			printf("\n%02x - ",i);
  		printf("%02x ", (*XFS_ptr++ & 0x00ff) );
  	       }
	printf("\n");  	       			 
  	fflush(stdout);			*/

#ifdef GOOD_XFREEFONT 
  if (_w->ztach.ufont != _w->ztach.font) /* hopefully always true if same font */
  	XFreeFont(XtDisplay(_w),_w->ztach.ufont);
#endif

/*  printf("...Callbacks");
  fflush(stdout);			*/
  XtRemoveAllCallbacks((Widget)_w, XtNincrementCallback);
  XtRemoveAllCallbacks((Widget)_w, XtNdecrementCallback);
  XtRemoveAllCallbacks((Widget)_w, XtNvalueChangeCallback);
/*  printf("...Complete");
  fflush(stdout);			*/
}



static void DrawArm(_w, _gc)
ZtachWidget _w; 
GC _gc;
{    
	double 	angle, cos_angle, sin_angle;
	int	cx_0, cx_1, cy_0, cy_1;
	int     topx_0, topx_1, topy_0, topy_1;
	double	half_ptr_width = 2;             /* sort of a mis-nomer */
	double  half_tacharm_width;
	XPoint  tacharm[3];
	int	small_tach;

if (_w->ztach.drawTachArm == FALSE)
 {
 	if (_w->ztach.drawAhead == TRUE)
  		DrawArrow(_w, _gc);

	if (_w->ztach.drawHole == TRUE)
					/* this is the center dot */
  		XFillArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
    			_w->ztach.center.x - (_w->ztach.length / 20),
    			_w->ztach.center.y - (_w->ztach.length / 20),    
    			_w->ztach.length / 10, _w->ztach.length / 10, 0, 360*64);

					/* this is the indicator arm */
  	XDrawLine(XtDisplay(_w), XtWindow(_w), _gc,
    		_w->ztach.center.x,
    		_w->ztach.center.y,
    		_w->ztach.top.x,
    		_w->ztach.top.y);
    		
    		
    			/* lets try a little thicker marker        */
			/* the math may eat you up, unfortunately  */
			/* .......actually its not too bad         */
			/* this is not perfect for mathematical reasons */
			/* best left explained by the philosopher kings  */
    
    if (_w->ztach.thickBits == TRUE)				
       {	
	angle = (_w->ztach.value - _w->ztach.minimum) / 
		 	(double)(_w->ztach.maximum - _w->ztach.minimum) * 270.0;
	angle -= 45.0;

	if (_w->ztach.rotateCW == FALSE)
		angle = 180 - angle;		/* reverse the phase */

	cos_angle = cos(RADIANS(angle));
	sin_angle = sin(RADIANS(angle));	


	cx_0 = _w->ztach.center.x - (int)(sin_angle * half_ptr_width);
	cx_1 = _w->ztach.center.x + (int)(sin_angle * half_ptr_width);
	cy_0 = _w->ztach.center.y + (int)(cos_angle * half_ptr_width);
	cy_1 = _w->ztach.center.y - (int)(cos_angle * half_ptr_width);

	topx_0 = cx_0 - (int)(cos_angle * (_w->ztach.length-8) * 7.0/8.0);
	topy_0 = cy_0 - (int)(sin_angle * (_w->ztach.length-8) * 7.0/8.0);
	topx_1 = cx_1 - (int)(cos_angle * (_w->ztach.length-8) * 7.0/8.0);	
	topy_1 = cy_1 - (int)(sin_angle * (_w->ztach.length-8) * 7.0/8.0);	

   	XDrawLine(XtDisplay(_w), XtWindow(_w), _gc, cx_0, cy_0, topx_0, topy_0);
   	XDrawLine(XtDisplay(_w), XtWindow(_w), _gc, cx_1, cy_1, topx_1, topy_1);    

/* the values (hpw=2) form a sinusoid varying 0 +1 0 -1 0 (but you knew that) */ 
/*	printf("\ncx:%d cy:%d tx:%d ty:%d", _w->ztach.center.x, _w->ztach.center.y,
					_w->ztach.top.x, _w->ztach.top.y);	
	printf("   x0:%d x1:%d y0:%d y1:%d tx0:%d ty0:%d tx1:%d ty1:%d",
			cx_0, cx_1, cy_0, cy_1, topx_0, topy_0, topx_1, topy_1);
	printf("\nSin: %2.2f  Cos: %2.2f   (int)sin * hpw: %d  ditto cos: %d",
		sin_angle, cos_angle, (int)(sin_angle * half_ptr_width),
					(int)(cos_angle * half_ptr_width));    
	fflush(1);
*/	
       } 
  }
else	
  {				/* do the real tach arm */			
	if (_w->ztach.length < 101)
		small_tach = 1;
	else
		small_tach = 0;
				

	if (_w->ztach.drawHole == TRUE )
	    XFillArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
    		_w->ztach.center.x - (_w->ztach.length / (small_tach ? 10 :20)),
    		_w->ztach.center.y - (_w->ztach.length / (small_tach ? 10 :20)),    
    		_w->ztach.length / (small_tach ? 5 : 10), 
    		_w->ztach.length / (small_tach ? 5 : 10), 0, 360*64);                
	
	angle = (_w->ztach.value - _w->ztach.minimum) / 
		 	(double)(_w->ztach.maximum - _w->ztach.minimum) * 270.0;
	angle -= 45.0;

	if (_w->ztach.rotateCW == FALSE)
		angle = 180 - angle;		/* reverse the phase */

	cos_angle = cos(RADIANS(angle));
	sin_angle = sin(RADIANS(angle));

	if (small_tach)        /* small tachs need bigger arms */
		half_tacharm_width = (int)(_w->ztach.length / 7.5);
	else	
		half_tacharm_width = (int)(_w->ztach.length / 15);
	tacharm[0].x = _w->ztach.center.x - (int)(sin_angle * half_tacharm_width);
	tacharm[2].x = _w->ztach.center.x + (int)(sin_angle * half_tacharm_width);
	tacharm[0].y = _w->ztach.center.y + (int)(cos_angle * half_tacharm_width);
	tacharm[2].y = _w->ztach.center.y - (int)(cos_angle * half_tacharm_width);
	tacharm[1].x = _w->ztach.top.x;
	tacharm[1].y = _w->ztach.top.y;	
	XFillPolygon(XtDisplay(_w), XtWindow(_w), _gc, tacharm, 3, 
						Convex, CoordModeOrigin);
  }
}



static void DrawLabel(_w, _gc)
ZtachWidget _w; 
GC _gc;
{
  XDrawString(XtDisplay(_w), XtWindow(_w), _gc,
    _w->ztach.labelPos.x, _w->ztach.labelPos.y,
    _w->ztach.label, strlen(_w->ztach.label));
}



static void DrawULabel(_w, _gc)
ZtachWidget _w; 
GC _gc;
{
  XDrawString(XtDisplay(_w), XtWindow(_w), _gc,
    _w->ztach.UlabelPos.x, _w->ztach.UlabelPos.y,
    _w->ztach.Ulabel, strlen(_w->ztach.Ulabel));
}



#define NUMSEGMENTS 20 

static void Redisplay(_w, _event, _region)
ZtachWidget _w; 
XEvent *_event; 
Region _region;
{
  int i;
  XPoint top, bot;
  int length;
  double angle, rangle;
  int ns;

  if (_w->core.visible)
  {
    XClearWindow(XtDisplay(_w), XtWindow(_w));

    DrawArm(_w, _w->ztach.gc);  /* XDrawArc: 3 oclock = 0 degress */
				/* dis, drawbl, gc, x,y, w,h, ang1, a2 (64ths)*/
    
    length = (int)(_w->ztach.length) * 2;
    

	if(_w->ztach.drawArc == TRUE)
    		XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc, 
      				_w->ztach.center.x - (int)_w->ztach.length,
      				_w->ztach.center.y - (int)_w->ztach.length,
      				length, length, -45 * 64, 270 * 64);

/* A Double arc might look better */

#ifdef GOOD_XDRAWARC
 	if(_w->ztach.thickBits)	
		XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc, 
      			_w->ztach.center.x - (int)(_w->ztach.length + 1),
      			_w->ztach.center.y - (int)(_w->ztach.length + 1),
      			length + 1, length + 1, -45 * 64, 270 * 64);	  

#else
/* whose bug is this?  The points on the arc nearest the tangent appear
   to be double wide, while those furthest away are not.
   Let us attempt to fool the lousy arc-inator		*/
   
	/* 2nd 'arc' is drawn in two bits, 1st 135 then 1 pix back, do rest*/   
        /* this isn't perfect, buts its better than the 'correct' way */ 
 
    if(_w->ztach.thickBits && _w->ztach.drawArc)
       {   
   	XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc, 
      			_w->ztach.center.x - (int)_w->ztach.length,
      			_w->ztach.center.y - (int)_w->ztach.length,
      			length + 1, length + 1, -45 * 64, 135 * 64);
	
	XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
      			_w->ztach.center.x - (int)(_w->ztach.length + 1),
      			_w->ztach.center.y - (int)(_w->ztach.length + 1),
      			length + 1, length + 1, 45 * 64, 180 * 64);

	/* the only bad parts are at 45 and 225 degrees, try to fix */
	
	XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
			_w->ztach.center.x - (int)(_w->ztach.length - 1),
			_w->ztach.center.y - (int)_w->ztach.length,
	          	length + 1, length + 1, 41 * 64, 8 * 64);
	                                                         
	XDrawArc(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
			_w->ztach.center.x - (int)(_w->ztach.length + 1),
			_w->ztach.center.y - (int)_w->ztach.length,
	          	length + 1, length + 1, 217 * 64, 8 * 64);                                                         
       } 
#endif
    	ns = MIN(NUMSEGMENTS, _w->ztach.maximum - _w->ztach.minimum);


	if (_w->ztach.drawMarkers == TRUE)
    	    for(i=0;i<=ns;i++)
    	       {
      		angle = 270.0/ns*i;
      		angle -= 45;		/* nicer */
      		rangle = RADIANS(angle);
      		top.x = _w->ztach.center.x - (int)(cos(rangle)*_w->ztach.length);
      		top.y = _w->ztach.center.y - (int)(sin(rangle)*_w->ztach.length);
	
    		if ( ((int)(i / 2) * 2) == i)       /* draw odd ones smaller */
     		       {		
      			bot.x = _w->ztach.center.x - 
      				(int)(cos(rangle)*_w->ztach.length*7.1/8.0);
      			bot.y = _w->ztach.center.y - 
      				(int)(sin(rangle)*_w->ztach.length*7.1/8.0);
     		       } 
    		else
     		       {		
      			bot.x = _w->ztach.center.x - 
      				(int)(cos(rangle)*_w->ztach.length*7.7/8.0);
      			bot.y = _w->ztach.center.y - 
      				(int)(sin(rangle)*_w->ztach.length*7.7/8.0);
     		       }
      		XDrawLine(XtDisplay(_w), XtWindow(_w), _w->ztach.gc,
        					top.x, top.y, bot.x, bot.y);
    	       }

	if (_w->ztach.drawNumstring)
    		DrawLabel(_w, _w->ztach.labelGC);
    	DrawULabel(_w, _w->ztach.UlabelGC);    
  }
  XFlush(XtDisplay(_w));
}



#define ZTACH _new->ztach

static Boolean SetValues(_current, _request, _new)
ZtachWidget _current; 
ZtachWidget _request; 
ZtachWidget _new;
{
  Boolean redisplay = FALSE;
  Display *display = XtDisplay(_new);
  Boolean newErase = FALSE, newLabel = FALSE, newuLabel = FALSE;
  XGCValues eraseGCValues, labelGCValues, gcVal;
  int eraseMask = GCFont, labelMask = GCFont;
 
 /* these first two are necessary even for a newly created tach if the
    values are set after it is created (even before it is realized) */
 
 	if (ZTACH.rotateCW != _current->ztach.rotateCW)
 		MoveArm(_new);
 
	if (strlen(ZTACH.Ulabel) != strlen(_current->ztach.Ulabel))
	       {
		ZTACH.UlabelHeight = ZTACH.ufont->ascent + ZTACH.ufont->descent;
		ZTACH.UlabelWidth = XTextWidth(ZTACH.ufont, ZTACH.Ulabel, 
							strlen(ZTACH.Ulabel));
		ZTACH.UlabelPos.x = ZTACH.center.x - (ZTACH.UlabelWidth / 2);
		redisplay = TRUE;
	       }	
	
	if (ZTACH.ufont == (XFontStruct *)NULL)
  	   {
    	    if ((ZTACH.ufont = XLoadQueryFont(display, "fixed")) == NULL)
    		if ((ZTACH.ufont = XLoadQueryFont(display, "9x15")) == NULL)
      			MyXtWarning("ZtachWidget: Fonts %s and %s not found.",
      							 "fixed", "9x15");
    	    redisplay = TRUE;
  	   }

  	if (ZTACH.UlabelForeground != _current->ztach.UlabelForeground)
  	       {
    		newuLabel = TRUE;
    		labelMask |= GCForeground;
    		labelGCValues.foreground = ZTACH.UlabelForeground; 
    		redisplay = TRUE;
  	       }

	if (ZTACH.ufont->fid != _current->ztach.ufont->fid)
  	       {
    		XFreeFont(display, _current->ztach.ufont);
    		newErase = newuLabel = TRUE;
    		redisplay = TRUE;
  	       }
  	eraseGCValues.font = ZTACH.ufont->fid;
  	labelGCValues.font = ZTACH.ufont->fid;

	if (newuLabel)
  	       {
    		XtReleaseGC((Widget)_new, _new->ztach.UlabelGC);
    		_new->ztach.UlabelGC = XtGetGC((Widget)_new, labelMask, 
    								&labelGCValues);
  	       }

 
  if (ZTACH.font == (XFontStruct *)NULL)
  {
    if ((ZTACH.font = XLoadQueryFont(display, "fixed")) == NULL)
    if ((ZTACH.font = XLoadQueryFont(display, "9x15")) == NULL)
      MyXtWarning("ZtachWidget: Fonts %s and %s not found.", "fixed", "9x15");
    redisplay = TRUE;
  }

  if (ZTACH.minimum >= ZTACH.maximum)
  {
    MyXtWarning("ZtachWidget: Maximum %d is less than minimum (%d).  Maximum set to %d.",
      ZTACH.maximum, ZTACH.maximum, ZTACH.minimum+1);
    ZTACH.maximum = ZTACH.minimum+1;
  }

  if (ZTACH.value < ZTACH.minimum)
  {
    MyXtWarning("ZtachWidget: Value %d is less than minimum (%d).  Value set to %d.",
      ZTACH.value, ZTACH.minimum, ZTACH.minimum);
    ZTACH.value = ZTACH.minimum;
  }

  if (ZTACH.value > ZTACH.maximum)
  {
    MyXtWarning("ZtachWidget: Value %d is more than maximum (%d).  Value set to %d.",
      ZTACH.value, ZTACH.maximum, ZTACH.maximum);
    ZTACH.value = ZTACH.maximum;
  }

  if (ZTACH.labelForeground != _current->ztach.labelForeground)
  {
    newLabel = TRUE;
    labelMask |= GCForeground;
    labelGCValues.foreground = ZTACH.labelForeground; 
    redisplay = TRUE;
  }

  if (ZTACH.foreground != _current->ztach.foreground)
  {
    XtReleaseGC((Widget)_new, ZTACH.gc);
    gcVal.foreground = ZTACH.foreground;
    ZTACH.gc = XtGetGC((Widget)_new, GCForeground, &gcVal);
    redisplay = TRUE;
  }

  if (_new->core.background_pixel != _current->core.background_pixel)
  {
    eraseGCValues.foreground = _new->core.background_pixel;
    newErase = TRUE;
    eraseMask |= GCForeground;
    redisplay = TRUE;
  }

  if (ZTACH.font->fid != _current->ztach.font->fid)
  {
    XFreeFont(display, _current->ztach.font);
    newErase = newLabel = TRUE;
    redisplay = TRUE;
  }

  eraseGCValues.font = ZTACH.font->fid;
  labelGCValues.font = ZTACH.font->fid;

  if (newErase)
  {
    XtReleaseGC((Widget)_new, _new->ztach.eraseGC);
    _new->ztach.eraseGC = XtGetGC((Widget)_new, eraseMask, &eraseGCValues);
  }

  if (newLabel)
  {
    XtReleaseGC((Widget)_new, _new->ztach.labelGC);
    _new->ztach.labelGC = XtGetGC((Widget)_new, labelMask, &labelGCValues);
  }

  if (ZTACH.value != _current->ztach.value)
  {
    if (!redisplay)
    {
      DrawArm(_current, ZTACH.eraseGC);
      if (ZTACH.drawNumstring == TRUE)
      	DrawLabel(_current, ZTACH.eraseGC);
      ReValue(_new);
      DrawArm(_new, ZTACH.gc);
      if (ZTACH.drawNumstring == TRUE)
      	DrawLabel(_new, ZTACH.labelGC);
      return FALSE;
    }
    ReValue(_new);
  }

  return redisplay;
}

#undef ZTACH



static void Increment(_w, _event, _argv, _argc)
ZtachWidget _w; 
XEvent *_event; 
String *_argv; 
int *_argc;
{
  int i;
  int newVal;
  Arg warg;
  ZtachCallbackStruct hdcs;

  if (*_argc == 0)
  {
    if ((_w->ztach.value + 1) > _w->ztach.maximum)
      return;
    XtSetArg(warg, XtNvalue, _w->ztach.value + 1);
    XtSetValues((Widget)_w, &warg, 1);
    return;
  }
  i = atoi(_argv[0]);
  if (i > 0)
  {
    newVal = ((_w->ztach.value + i) > _w->ztach.maximum) ? 
    				_w->ztach.maximum : _w->ztach.value + i;
    XtSetArg(warg, XtNvalue, newVal);
    XtSetValues((Widget)_w, &warg, 1);
  }
  hdcs.reason = ZTACH_INCREMENT;
  hdcs.event = _event;
  hdcs.value = _w->ztach.value;
  XtCallCallbacks((Widget)_w, XtNincrementCallback, &hdcs);
}



static void Decrement(_w, _event, _argv, _argc)
ZtachWidget _w; 
XEvent *_event;
String *_argv;
int *_argc;
{
  int i;
  int newVal;
  Arg warg;
  ZtachCallbackStruct hdcs;

  if (*_argc == 0)
  {
    if ((_w->ztach.value - 1) < _w->ztach.minimum)
      return;
    XtSetArg(warg, XtNvalue, _w->ztach.value - 1);
    XtSetValues((Widget)_w, &warg, 1);
    return;
  }
  i = atoi(_argv[0]);
  if (i > 0)
  {
    newVal = ((_w->ztach.value - i) < _w->ztach.minimum) ? 
    				_w->ztach.minimum : _w->ztach.value - i;
    XtSetArg(warg, XtNvalue, newVal);
    XtSetValues((Widget)_w, &warg, 1);
  }
  hdcs.reason = ZTACH_DECREMENT;
  hdcs.event = _event;
  hdcs.value = _w->ztach.value;
  XtCallCallbacks((Widget)_w, XtNdecrementCallback, &hdcs);
}



/* Know that this function does its work based off the results from
   atan2, which does not use the same angle(radian) orientation as X  */
					  /* or the move_arm function */
#define SQR(a) ((a)*(a))
#define DIST(a,b,c,d) sqrt( (double)(SQR((a)-(c)) + SQR((b)-(d))) )

static void Set(_w, _event, _argv, _argc)
ZtachWidget _w; 
XEvent *_event;
String *_argv;
int *_argc;
{
  double x, y;
  double angle, rangle;
  int newVal;
  Arg warg;
  ZtachCallbackStruct hdcs;

  switch (_event->type)
  {
    case ButtonPress:
    case ButtonRelease:
      x = ((XButtonEvent *)_event)->x;
      y = ((XButtonEvent *)_event)->y;
      break;
    case KeyPress:
    case KeyRelease:
      x = (double)((XKeyEvent *)_event)->x;
      y = (double)((XKeyEvent *)_event)->y;
      break;
    default: return; break;
  }

  if (DIST(x,y,_w->ztach.center.x, _w->ztach.center.y) > _w->ztach.length)
    return;

  rangle = atan2((double)(y - _w->ztach.center.y), 
                 (double)(x - _w->ztach.center.x)); 
                 
  	if ( rangle < 0 )   	/* atan2 (clockwise) 0(3)to PI(9) to -PI(3) */
  		rangle += 2 * LOCAL_PI;         /* now 0 to 2PI agiitb */

  	
  	angle = DEGREES(rangle); 
  	/* printf("\nangle of mouse is %d degrees", (int)angle); fflush(1); */


  	if ((angle > 45.0) && (angle < 135.0))  
    		return;

			 
/* it would be less confusing not to use angle as the "output" variable here*/	
	if (angle > 45.0)
  		angle -= 135.0;		/* 270 + our orientation correction */
	else
		angle += 225.0;
	
	if (_w->ztach.rotateCW == TRUE)
  		newVal = _w->ztach.maximum * (angle / 270.0); 
	else
  		newVal = _w->ztach.maximum * ((270 - angle) / 270.0); 

  XtSetArg(warg, XtNvalue, newVal);
  XtSetValues((Widget)_w, &warg, 1);
  hdcs.reason = ZTACH_SET;
  hdcs.event = _event;
  hdcs.value = _w->ztach.value;
  XtCallCallbacks((Widget)_w, XtNvalueChangeCallback, &hdcs);
}



static void Drag(_w, _xme)
ZtachWidget _w; 
XMotionEvent *_xme;
{
  XButtonEvent xe;
  xe.type = ButtonPress;
  xe.x = _xme->x;
  xe.y = _xme->y;

  Set(_w, (XEvent *)&xe, NULL, NULL);
}



#define ZTACH _w->ztach

static void ReValue(_w)
ZtachWidget _w;
{
  sprintf(_w->ztach.label, "%d", _w->ztach.value);
  ZTACH.labelHeight = ZTACH.font->ascent + ZTACH.font->descent;
  ZTACH.labelWidth = XTextWidth(ZTACH.font, ZTACH.label, strlen(ZTACH.label));
  ZTACH.labelPos.x = ZTACH.center.x - (ZTACH.labelWidth / 2);
  
  							/* FOOL WITH LABEL POS */
  
  ZTACH.labelPos.y = ZTACH.center.y + 
  			(int)((_w->core.height - 2.0 * ZTACH.margin) / 4);
  MoveArm(_w);
}

#undef ZTACH



#define MAXSTRING 300

static void MyXtWarning(_format, va_alist)
char *_format;
va_dcl   /* stupid define already has a ; on it */
{
  va_list parms;
  char dest[MAXSTRING];

  va_start(parms);
  vsprintf(dest, _format, parms);
  va_end(parms);

  XtWarning(dest);
}
