/*
 * @(#)Dial.c
 *
 * Copyright 1994 - 2022  David A. Bagley, bagleyd AT verizon.net
 *
 * 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 author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Methods file for Dial */

#include "DialP.h"

#define RT_ANGLE 90.0
#define ST_ANGLE 180.0
#define RADIANS(x) (M_PI*(x)/ST_ANGLE)
#define DEGREES(x) ((x)/M_PI*ST_ANGLE)

static Boolean setValuesDial(Widget current, Widget request, Widget renew);
static void quitDial(DialWidget w, XEvent *event, char **args, int nArgs);
static void destroyDial(Widget old);
static void initializeDial(Widget request, Widget renew);
static void exposeDial(Widget renew, XEvent *event, Region region);
static void hideDial(DialWidget w, XEvent *event, char **args, int n_args);
static void resizeDial(DialWidget w);
static void selectDial(DialWidget w, XEvent *event, char **args, int n_args);
static void checkDial(DialWidget w);
static void enterDial(DialWidget w, XEvent *event, char **args, int nArgs);
static void leaveDial(DialWidget w, XEvent *event, char **args, int nArgs);
static void incrementDial(DialWidget w,
	XEvent *event, char **args, int nArgs);
static void decrementDial(DialWidget w,
	XEvent *event, char **args, int nArgs);

static char defaultTranslationsDial[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>Up: Increment()\n\
 <KeyPress>osfUp: Increment()\n\
 <KeyPress>KP_Up: Increment()\n\
 <KeyPress>KP_8: Increment()\n\
 <KeyPress>R8: Increment()\n\
 <KeyPress>Left: Decrement()\n\
 <KeyPress>osfLeft: Decrement()\n\
 <KeyPress>KP_Left: Decrement()\n\
 <KeyPress>KP_4: Decrement()\n\
 <KeyPress>R10: Decrement()\n\
 <KeyPress>Right: Increment()\n\
 <KeyPress>osfRight: Increment()\n\
 <KeyPress>KP_Right: Increment()\n\
 <KeyPress>KP_6: Increment()\n\
 <KeyPress>R12: Increment()\n\
 <KeyPress>Down: Decrement()\n\
 <KeyPress>osfDown: Decrement()\n\
 <KeyPress>KP_Down: Decrement()\n\
 <KeyPress>KP_2: Decrement()\n\
 <KeyPress>R14: Decrement()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <Btn1Down>: Select()\n\
 <Btn1Motion>: Select()\n\
 <Btn4Down>: Increment()\n\
 <Btn5Down>: Decrement()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";


static XtActionsRec actionsListDial[] =
{
	{(char *) "Quit", (XtActionProc) quitDial},
	{(char *) "Hide", (XtActionProc) hideDial},
	{(char *) "Increment", (XtActionProc) incrementDial},
	{(char *) "Decrement", (XtActionProc) decrementDial},
	{(char *) "Select", (XtActionProc) selectDial},
	{(char *) "Enter", (XtActionProc) enterDial},
	{(char *) "Leave", (XtActionProc) leaveDial}
};
static XtResource resourcesDial[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
	 XtOffset(DialWidget, core.width),
	 XtRString, (caddr_t) "100"},
	{XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
	 XtOffset(DialWidget, core.height),
	 XtRString, (caddr_t) "100"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
	 XtOffset(DialWidget, dial.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(DialWidget, dial.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNneedleColor, XtCColor, XtRPixel, sizeof(Pixel),
	 XtOffset(DialWidget, dial.needleColor),
	 XtRString, (caddr_t) "Blue"},
	{XtNmarkerColor, XtCColor, XtRPixel, sizeof(Pixel),
	 XtOffset(DialWidget, dial.markerColor),
	 XtRString, (caddr_t) "Green"},
	{XtNmarkers, XtCMarkers, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.markers),
	 XtRString, (caddr_t) "91"},
	{XtNmaxDegree, XtCMaxDegree, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.maxDegree),
	 XtRString, (caddr_t) "270"},
	{XtNfactor, XtCFactor, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.factor),
	 XtRString, (caddr_t) "100"},
	{XtNminimum, XtCMinimum, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.minimum),
	 XtRString, (caddr_t) "0"},
	{XtNmaximum, XtCMaximum, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.maximum),
	 XtRString, (caddr_t) "270"},
	{XtNmarkerLength, XtCPosition, XtRPosition, sizeof(Position),
	 XtOffset(DialWidget, dial.markerLength),
	 XtRString, (caddr_t) "10"},
	{XtNval, XtCVal, XtRInt, sizeof(int),
	 XtOffset(DialWidget, dial.val), XtRString, (caddr_t) "0"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(DialWidget, dial.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(DialWidget, dial.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(DialWidget, dial.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof(caddr_t),
	 XtOffset(DialWidget, dial.select),
	 XtRCallback, (caddr_t) NULL}
};

DialClassRec dialClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Dial",	/* class name */
		sizeof(DialRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializeDial,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsListDial,	/* actions */
		XtNumber(actionsListDial),	/* num actions */
		resourcesDial,		/* resources */
		XtNumber(resourcesDial),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyDial,	/* destroy */
		(XtWidgetProc) resizeDial,	/* resize */
		(XtExposeProc) exposeDial,	/* expose */
		(XtSetValuesFunc) setValuesDial,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		NULL,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		defaultTranslationsDial,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass dialWidgetClass = (WidgetClass) &dialClassRec;

static void
setDial(DialWidget w, int reason)
{
	dialCallbackStruct cb;

	cb.reason = reason;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}

static void
draw3DFrame(const DialWidget w, Pixmap dr, Boolean inside)
{
	GC gc;

	if (inside)
		gc = w->dial.inverseGC[2];
	else
		gc = w->dial.inverseGC[0];
	FILLRECTANGLE(w, dr, gc,
		0, 0, w->core.width, 1);
	FILLRECTANGLE(w, dr, gc,
		0, 1, 1, w->core.width - 1);
	FILLRECTANGLE(w, dr, gc,
		1, 1, w->core.width - 2, 1);
	FILLRECTANGLE(w, dr, gc,
		1, 1, 1, w->core.width - 3);
	if (inside)
		gc = w->dial.inverseGC[0];
	else
		gc = w->dial.inverseGC[2];
	FILLRECTANGLE(w, dr, gc,
		1, w->core.height - 1, w->core.width - 1, 1);
	FILLRECTANGLE(w, dr, gc,
		w->core.width - 1, 1, 1, w->core.height - 2);
	FILLRECTANGLE(w, dr, gc,
		2, w->core.height - 2, w->core.width - 3, 1);
	FILLRECTANGLE(w, dr, gc,
		w->core.width - 2, 2, 1, w->core.height - 4);
}

static void
eraseFrame(const DialWidget w, Pixmap dr, Boolean focus)
{
	if (focus) {
		draw3DFrame(w, dr, focus);
	} else {
		DRAWRECTANGLE(w, dr, w->dial.inverseGC[1],
		      0, 0, w->core.width - 1, w->core.height - 1);
		DRAWRECTANGLE(w, dr, w->dial.inverseGC[1],
		      1, 1, w->core.width - 3, w->core.height - 3);
	}
	FILLRECTANGLE(w, dr, w->dial.inverseGC[1],
		2, 2, w->core.width - 4, w->core.height - 4);

}

static void
drawFrame(DialWidget w, Pixmap dr, Boolean focus)
{
	if (focus) {
		draw3DFrame(w, dr, focus);
	} else {
		DRAWRECTANGLE(w, dr, w->dial.inverseGC[1],
			0, 0, w->core.width - 1, w->core.height - 1);
		DRAWRECTANGLE(w, dr, w->dial.inverseGC[1],
			1, 1, w->core.width - 3, w->core.height - 3);
	}
}

static void
drawNeedle(DialWidget w, GC gc)
{
	Pixmap *dr;
	Pixmap adr = 0;
	double sine, cosine;

	int needleLength = MAX(w->dial.dialRadius, 1);
	int needleWidth2 = needleLength / 10;
	int needleTail = needleLength / 10;
	int i;
	Point points[5];
	GC borderGC;

	dr = &adr;
	sine = sin (w->dial.angle);
	cosine = cos (w->dial.angle);

	points[0].x = (short) (w->dial.center.x - sine * needleWidth2);
	points[0].y = (short) (w->dial.center.y + cosine * needleWidth2);
	points[1].x = (short) (w->dial.center.x - cosine * needleLength);
	points[1].y = (short) (w->dial.center.y - sine * needleLength);
	points[2].x = (short) (w->dial.center.x + sine * needleWidth2);
	points[2].y = (short) (w->dial.center.y - cosine * needleWidth2);
	points[3].x = (short) (w->dial.center.x + cosine * needleTail);
	points[3].y = (short) (w->dial.center.y + sine * needleTail);
	points[4].x = points[0].x;
	points[4].y = points[0].y;
	if (w->dial.inverseGC[1] == gc)
		borderGC = w->dial.inverseGC[1];
	else
		borderGC = w->dial.borderGC;
	POLYGON(w, *dr, gc, borderGC, points, 4, True, True);
	for (i = 0; i < 4; i++) {
		int diff0, diff1;

		diff0 = points[i].y - points[i].x;
		diff1 = points[i + 1].y - points[i + 1].x;
		if (w->dial.inverseGC[1] != gc) {
			if (diff0 > diff1) {
				POLYLINE(w, *dr, w->dial.inverseGC[0],
					&(points[i]), 2, True);
			} else {
				POLYLINE(w, *dr, w->dial.inverseGC[2],
					&(points[i]), 2, True);
			}
		} else {
			POLYLINE(w, *dr, w->dial.inverseGC[1],
				&(points[i]), 2, True);
		}
	}
	DRAWLINE(w, *dr, borderGC,
		w->dial.center.x, w->dial.center.y,
		w->dial.center.x, w->dial.center.y);
	if (w->dial.inverseGC[1] == gc)
		XDrawSegments(XtDisplay(w), XtWindow(w),
			w->dial.markerGC, w->dial.segments, w->dial.markers);
}

static void
moveDial(DialWidget w , int dir)
{
	float avg, tac = w->dial.val;
	Position needleLength;
	dialCallbackStruct cb;

	if (ACTION_INCREMENT == dir) {
		if (tac / w->dial.factor >= w->dial.maximum)
			return;
		tac += w->dial.factor;
	}
	if (ACTION_DECREMENT == dir) {
		if (tac / w->dial.factor <= w->dial.minimum)
			return;
		tac -= w->dial.factor;
	}
	w->dial.val = (int) tac;
	tac /= w->dial.factor;
	if (tac > w->dial.maximum) {
		tac = w->dial.maximum;
		w->dial.val = (int) (w->dial.factor * tac);
	}
	if (tac < w->dial.minimum) {
		tac = w->dial.minimum;
		w->dial.val = (int) (w->dial.factor * tac);
	}
	needleLength = w->dial.dialRadius - w->dial.markerLength - 2;
	avg = (w->dial.maximum + w->dial.minimum) / 2.0;
	drawNeedle(w, w->dial.inverseGC[1]);
	w->dial.angle = tac * RADIANS(w->dial.maxDegree / 2) / avg +
		 RADIANS(ST_ANGLE - RT_ANGLE - w->dial.maxDegree / 2);
#ifdef DEBUG
	(void) printf("moveDial: angle %g, tac %g\n", w->dial.angle, tac);
#endif
	w->dial.needle.x = (short int) (w->dial.center.x -
		needleLength * cos(w->dial.angle));
	w->dial.needle.y = (short int) (w->dial.center.y -
		needleLength * sin(w->dial.angle));
	drawNeedle(w, w->dial.needleGC);
	cb.reason = ACTION_SELECTED;
	cb.val = w->dial.val;
	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
}

#define BRIGHT_FACTOR 0.8
#define DARK_FACTOR 0.75
#define MAX_INTENSITY 0xFFFF

static Pixel
brighter(DialWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));
	int i = (int) ((1 - BRIGHT_FACTOR) * MAX_INTENSITY);

	color.pixel = pixel;
	VOID XQueryColor(XtDisplay(w), colormap, &color);
	if (color.red < i)
		color.red = i;
	if (color.green < i)
		color.green = i;
	if (color.blue < i)
		color.blue = i;
	color.red = (unsigned short) MIN(color.red / BRIGHT_FACTOR, MAX_INTENSITY);
	color.green = (unsigned short) MIN(color.green / BRIGHT_FACTOR, MAX_INTENSITY);
	color.blue = (unsigned short) MIN(color.blue / BRIGHT_FACTOR, MAX_INTENSITY);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static Pixel
darker(DialWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));

	color.pixel = pixel;
	VOID XQueryColor(XtDisplay(w), colormap, &color);
	color.red = (unsigned short) (color.red * DARK_FACTOR);
	color.green = (unsigned short) (color.green * DARK_FACTOR);
	color.blue = (unsigned short) (color.blue * DARK_FACTOR);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static void
setAllColors(DialWidget w)
{
	XGCValues values;
	XtGCMask valueMask;

	valueMask = GCForeground | GCBackground;
	if (w->dial.reverse) {
		values.background = w->dial.foreground;
		values.foreground = w->dial.background;
	} else {
		values.foreground = w->dial.background;
		values.background = w->dial.foreground;
	}
	if (w->dial.inverseGC[1])
		XtReleaseGC((Widget) w, w->dial.inverseGC[1]);
	w->dial.inverseGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->dial.mono) {
		values.foreground = brighter(w, (w->dial.reverse) ?
			w->dial.foreground : w->dial.background);
	}
	if (w->dial.inverseGC[0])
		XtReleaseGC((Widget) w, w->dial.inverseGC[0]);
	w->dial.inverseGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->dial.mono) {
		values.foreground = darker(w, (w->dial.reverse) ?
			w->dial.foreground : w->dial.background);
	}
	if (w->dial.inverseGC[2])
		XtReleaseGC((Widget) w, w->dial.inverseGC[2]);
	w->dial.inverseGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (w->dial.reverse) {
		if (w->dial.mono) {
			values.background = w->dial.foreground;
			values.foreground = w->dial.background;
		} else {
			values.background = w->dial.needleColor;
			values.foreground = w->dial.borderColor;
		}
	} else {
		if (w->dial.mono) {
			values.foreground = w->dial.foreground;
			values.background = w->dial.background;
		} else {
			values.foreground = w->dial.needleColor;
			values.background = w->dial.borderColor;
		}
	}
	if (w->dial.reverse) {
		if (w->dial.mono) {
			values.foreground = w->dial.foreground;
			values.background = w->dial.background;
		} else {
			values.foreground = w->dial.needleColor;
			values.background = w->dial.borderColor;
		}
	} else {
		if (w->dial.mono) {
			values.background = w->dial.foreground;
			values.foreground = w->dial.background;
		} else {
			values.background = w->dial.needleColor;
			values.foreground = w->dial.borderColor;
		}
	}
	if (w->dial.borderGC)
		XtReleaseGC((Widget) w, w->dial.borderGC);
	w->dial.borderGC = XtGetGC((Widget) w, valueMask, &values);
	values.foreground = w->dial.markerColor;
	values.background = w->dial.background;
	if (w->dial.markerGC)
		XtReleaseGC((Widget) w, w->dial.markerGC);
	w->dial.markerGC = XtGetGC((Widget) w, valueMask, &values);
	values.foreground = w->dial.needleColor;
	if (w->dial.needleGC)
		XtReleaseGC((Widget) w, w->dial.needleGC);
	w->dial.needleGC = XtGetGC((Widget) w, valueMask, &values);
}

static void
initializeDial(Widget request, Widget renew)
{
	DialWidget w = (DialWidget) renew;

	w->dial.focus = False;
	w->dial.factor = 100;
	checkDial(w);
	resizeDial(w);
}

static void
destroyDial(Widget old)
{
	DialWidget w = (DialWidget) old;
	int i;

	XtReleaseGC(old, w->dial.needleGC);
	XtReleaseGC(old, w->dial.markerGC);
	for (i = 0; i < BG_SHADES; i++)
		XtReleaseGC(old, w->dial.inverseGC[i]);
	XtRemoveCallbacks(old, XtNselectCallback, w->dial.select);
}

static void
resizeDial(DialWidget w)
{
	double angle, cosine, sine, increment;
	int i, innerRadius, innerRadius2, radius, num, mod = 1;
	XSegment *ptr;

	num = w->dial.maxDegree / 90;
	if (num > 0)
		mod = (w->dial.markers - 1) / num;
	if (mod > 9)
		mod /= 3;
	while (mod > 8)
		mod /= 2;
	if (mod <= 0)
		mod = 1;
	ptr = w->dial.segments;
	w->dial.center.x = w->core.width / 2;
	w->dial.center.y = w->core.height / 2;
	increment = RADIANS(w->dial.maxDegree) / (float) (w->dial.markers - 1);
	w->dial.dialRadius = 9 * MIN(w->core.width, w->core.height) / 20;
	innerRadius = w->dial.dialRadius - w->dial.markerLength;
	innerRadius2 = w->dial.dialRadius - w->dial.markerLength / 2;
	angle = RADIANS(w->dial.maxDegree / 2.0 - w->dial.maxDegree);
	for (i = 0; i < w->dial.markers; i++) {
		cosine = cos(angle);
		sine = sin(angle);
		ptr->x1 = (short int) (w->dial.center.x +
			w->dial.dialRadius * sine);
		ptr->y1 = (short int) (w->dial.center.y -
			w->dial.dialRadius * cosine);
		if (i % mod == 0) {
			radius = innerRadius;
		} else {
			radius = innerRadius2;
		}
		ptr->x2 = (short int) (w->dial.center.x +
			radius * sine);
		ptr++->y2 = (short int) (w->dial.center.y -
			radius * cosine);
		angle += increment;
	}
	calculateDial(w);
	setAllColors(w);
}

static void
exposeDial(Widget renew, XEvent *event, Region region)
{
	DialWidget w = (DialWidget) renew;

	if (!w->core.visible)
		return;
	eraseFrame(w, 0, w->dial.focus);
	XDrawSegments(XtDisplay(w), XtWindow(w),
		w->dial.markerGC, w->dial.segments, w->dial.markers);
	drawNeedle(w, w->dial.needleGC);
}

static void
hideDial(DialWidget w, XEvent *event, char **args, int nArgs)
{
	setDial(w, ACTION_HIDE);
}

static Boolean
setValuesDial(Widget current, Widget request, Widget renew)
{
	DialWidget c = (DialWidget) current, w = (DialWidget) renew;
	Boolean redraw = False, redrawPointer = False;

	checkDial(w);
	if (w->dial.background != c->dial.background ||
			w->dial.foreground != c->dial.foreground ||
			w->dial.borderColor != c->dial.borderColor ||
			w->dial.needleColor != c->dial.needleColor ||
			w->dial.markerColor != c->dial.markerColor) {
		setAllColors(w);
		redraw = True;
	}
	if (w->dial.val != c->dial.val ||
			w->dial.minimum != c->dial.minimum ||
			w->dial.maximum != c->dial.maximum) {
		calculateDial(w);
		redrawPointer = True;
	}
	if (w->dial.menu != ACTION_IGNORE) {
		int menu = w->dial.menu;

		w->dial.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_INCREMENT:
		case ACTION_DECREMENT:
			moveDial(w, menu);
			break;
		default:
			break;
		}
	}
	if (redrawPointer && !redraw && XtIsRealized(renew) &&
			renew->core.visible) {
		drawNeedle(c, w->dial.inverseGC[1]);
		drawNeedle(w, w->dial.needleGC);
	}
	return (redraw);
}

static void
selectDial(DialWidget w, XEvent *event, char **args, int n_args)
{
	int pos;
	double angle;
	float avg, tac;
	dialCallbackStruct cb;
	int x, y;

	pos = w->dial.val;
	if (event->type == ButtonPress || event->type == MotionNotify) {
		x = (event->xbutton.y - w->dial.center.y);
		y = (event->xbutton.x - w->dial.center.x);
		if (x != 0 || y != 0) {
			angle = atan2((double) y, (double) -x);
		} else
			angle = 0;
		avg = (w->dial.maximum + w->dial.minimum) / 2.0;
		pos = (int) (w->dial.factor * (avg + angle * avg /
			RADIANS(w->dial.maxDegree / 2.0)));
		if (pos < w->dial.factor * w->dial.minimum)
			 pos = w->dial.factor * w->dial.minimum;
		if (pos > w->dial.factor * w->dial.maximum)
			 pos = w->dial.factor * w->dial.maximum;
		tac = pos;
		tac /= w->dial.factor;
#ifdef DEBUG
		(void) printf("selectDial: angle %g, tac %g\n", angle, tac);
#endif
	}
	cb.reason = ACTION_SELECTED;
	cb.event = event;
	cb.val = pos;
	XtCallCallbacks((Widget) w, XtNselectCallback, &cb);
}


static void
checkDial(DialWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->dial.minimum >= w->dial.maximum) {
		intCat(&buf1, "Maximum (", w->dial.maximum);
		stringCat(&buf2, buf1, ") must be greater than minimum (");
		free(buf1);
		intCat(&buf1, buf2, w->dial.minimum);
		free(buf2);
		stringCat(&buf2, buf1, "), minimum defaulting to ");
		free(buf1);
		w->dial.minimum = w->dial.maximum - 1;
		intCat(&buf1, buf2, w->dial.minimum);
		free(buf2);
		XtWarning(buf1);
		free(buf1);
	}
	if (w->dial.val < w->dial.factor * w->dial.minimum &&
			w->dial.val > w->dial.factor * w->dial.maximum) {
		intCat(&buf1, "Dial value out of bounds, use ",
			w->dial.minimum);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, w->dial.maximum);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->dial.minimum);
		free(buf2);
		XtWarning(buf1);
		free(buf1);
		w->dial.val = w->dial.factor * w->dial.minimum;
	}
	if (w->dial.markers < 2 && w->dial.markers > MAX_SEGMENTS / 2) {
		w->dial.markers = MAX_SEGMENTS / 2;
		intCat(&buf1, "Dial value out of bounds, use 2..",
			MAX_SEGMENTS / 2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->dial.markers);
		free(buf2);
		XtWarning(buf1);
		free(buf1);
	}
}

static void
quitDial(DialWidget w, XEvent *event, char **args, int nArgs)
{
	XtCloseDisplay(XtDisplay(w));
	exit(0);
}

void
calculateDial(DialWidget w)
{
	float avg, tac = w->dial.val;
	Position needleLength;

	tac /= w->dial.factor;
	needleLength = w->dial.dialRadius - w->dial.markerLength - 2;
	avg = (w->dial.maximum + w->dial.minimum) / 2.0;
	w->dial.angle = tac * RADIANS(w->dial.maxDegree / 2) / avg +
		 RADIANS(ST_ANGLE - RT_ANGLE - w->dial.maxDegree / 2);
#ifdef DEBUG
	(void) printf("calculateDial: angle %g, tac %g\n", w->dial.angle, tac);
#endif
	w->dial.needle.x = (short int) (w->dial.center.x -
		needleLength * cos(w->dial.angle));
	w->dial.needle.y = (short int) (w->dial.center.y -
		needleLength * sin(w->dial.angle));
}

static void
enterDial(DialWidget w , XEvent *event, char **args, int nArgs)
{
	w->dial.focus = True;
	drawFrame(w, 0, w->dial.focus);
}

static void
leaveDial(DialWidget w, XEvent *event, char **args, int nArgs)
{
	w->dial.focus = False;
	drawFrame(w, 0, w->dial.focus);
}

static void
incrementDial(DialWidget w, XEvent *event, char **args, int nArgs)
{
	moveDial(w, ACTION_INCREMENT);
}

static void
decrementDial(DialWidget w, XEvent *event, char **args, int nArgs)
{
	moveDial(w, ACTION_DECREMENT);
}
