/*
 * @(#)xthreed.c
 *
 * Copyright 1994 - 2020  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.
 */

/* Driver file for Threed */

#ifndef WINVER
#include "version.h"
static const char aboutHelp[] = {
"Threed Version " VERSION "\n"
"Send bugs (reports or fixes) to the author: "
"David Bagley <bagleyd AT verizon.net>\n"
"The latest version is at: "
"https://www.sillycycle.com/puzzles.html\n"
"Originally written by Thomas W. Olsen "
"in the \"The C Users Journal\"."
};

static const char synopsisHelp[] = {
"[-geometry [{width}][x{height}][{+-}{xoff}[{+-}{yoff}]]]\n"
"[-display [{host}]:[{vs}]] [-{foreground|fg} {color}]\n"
"[-{background|bg} {color}] [-frame {color}]\n"
"[-white {color}] [-[lt|dk]gray {color}] [-black {color}]\n"
"[-another {color}] [-{border|bd} {color}] [-delay msecs]\n"
"[-[no]sound] [-bumpSound {filename}]\n"
"[-moveSound {filename}] [-[no]surface]\n"
"[-object {int}] [-x {int}] [-y {int}] [-z {int}]\n"
"[-theta {int}] [-phi {int}] [psi {int}] [-version]"
};
#endif

#if defined(HAVE_MOTIF) || defined(WINVER)
static const char descriptionHelp[] = {
"A simple 3D viewer (though may need some work).\n"
"\n"
"The roll part does not seem to be quite right and may not\n"
"track well.  Of note is the mouse to 3D surface detection.\n"
"\n"
"In the sample data, Cubes and F16 are big, so the polyhedrons\n"
"are scaled up to them in the data file."
};

static const char featuresHelp[] = {
"Press \"L\" or \"l\" keys to move object left.\n"
"\n"
"Press \"R\" or \"r\" keys to move object right.\n"
"\n"
"Press \"U\" or \"u\" keys to move object up.\n"
"\n"
"Press \"D\" or \"d\" keys to move object down.\n"
"\n"
"Press \"I\" or \"i\" keys to move object in.\n"
"\n"
"Press \"O\" or \"o\" keys to move object out.\n"
"\n"
"Press \"S\" or \"s\" keys to change surface.\n"
"\n"
"Press \"B\" or \"b\" keys to change object.\n"
"\n"
"Press \"@\" key to toggle the sound.\n"
"\n"
"Press \"Esc\" key to hide program.\n"
"\n"
"Press \"Q\", \"q\", or \"CTRL-C\" keys kill program.\n"
"\n"
"Use the key pad or arrow keys to rotate object without the mouse.\n"
"Key pad is defined for Threed as:\n"
"  /     Counterclockwise\n"
"  8     Up\n"
"  ^\n"
"4<5>6   Left, Clockwise, Right\n"
"  v\n"
"  2     Down"
};

static const char referencesHelp[] = {
"Original code by\n"
"Thomas W. Olsen \"The C Users Journal\".\n"
"Andre LaMothe \"Black Art of 3D Game Programming\".\n"
"Arash Partow \"http://www.partow.net\"."
};
#endif

#ifdef WINVER
#include "ThreedP.h"
#define TITLE "wthreed"

static ThreeDRec widget;

#define PRINT_MESSAGE(b) (void) MessageBox(widget.core.hWnd, (LPCSTR) b, "Warning", MB_OK);

static void
initialize(ThreeDWidget w, HBRUSH brush)
{
	HMENU hMenu;

	initializeThreeD(w, brush);

	hMenu = GetMenu(w->core.hWnd);
	(void) CheckMenuItem(hMenu, (unsigned int)
		w->threed.object + ACTION_TETRA, MF_CHECKED);
}

void
setThreed(ThreeDWidget w, int reason)
{
	HMENU hMenu;

	switch (reason) {
	case ACTION_HIDE:
		ShowWindow(w->core.hWnd, SW_SHOWMINIMIZED);
		break;
	case ACTION_SURFACE:
		w->threed.surface = !w->threed.surface;
		break;
	case ACTION_OBJECT:
		hMenu = GetMenu(w->core.hWnd);
		if (w->threed.numObjects > 0) {
			(void) CheckMenuItem(hMenu, (unsigned int)
				w->threed.object + ACTION_TETRA, MF_UNCHECKED);
			w->threed.object = (w->threed.object + 1) %
				w->threed.numObjects;
			(void) CheckMenuItem(hMenu, (unsigned int)
				w->threed.object + ACTION_TETRA, MF_CHECKED);
		}
		break;
	}
}

static LRESULT CALLBACK
about(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_COMMAND && LOWORD(wParam) == IDOK) {
		(void) EndDialog(hDlg, TRUE);
		return TRUE;
	}
	return FALSE;
}

static LRESULT CALLBACK
WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HBRUSH brush = (HBRUSH) NULL;
	HMENU hMenu;
	PAINTSTRUCT paint;
	int vPos, hPos;
	static Boolean mousePressed = False;

	widget.core.hWnd = hWnd;
	switch (message) {
	case WM_CREATE:
		initialize(&widget, brush);
		break;
	case WM_DESTROY:
		destroyThreeD(brush);
		break;
	case WM_SIZE:
		(void) InvalidateRect(widget.core.hWnd, NULL, TRUE);
		break;
	case WM_PAINT:
		widget.core.hDC = BeginPaint(widget.core.hWnd, &paint);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_BRUSH));
		exposeThreeD(&widget);
		(void) EndPaint(hWnd, &paint);
		break;
	case WM_LBUTTONDOWN:
		mousePressed = True;
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		selectThreeD(&widget, LOWORD(lParam), HIWORD(lParam)
			/*, (GetKeyState(VK_CONTROL) >> 1) ? 1 : 0*/);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_MOUSEMOVE:
		if (!(wParam & MK_LBUTTON)) {
			if (mousePressed) {
				mousePressed = False;
				widget.core.hDC = GetDC(hWnd);
				(void) SelectObject(widget.core.hDC,
					GetStockObject(NULL_PEN));
				releaseThreeD(&widget/*, LOWORD(lParam), HIWORD(lParam)
					((GetKeyState(VK_SHIFT) >> 1) || (GetKeyState(VK_CAPITAL) & 1)),
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0*/);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		}
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		motionThreeD(&widget, LOWORD(lParam), HIWORD(lParam)
			/*, ((GetKeyState(VK_SHIFT) >> 1) || (GetKeyState(VK_CAPITAL) & 1)),
				(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0*/);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONUP:
		mousePressed = False;
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		releaseThreeD(&widget/*, LOWORD(lParam), HIWORD(lParam)
			((GetKeyState(VK_SHIFT) >> 1) || (GetKeyState(VK_CAPITAL) & 1)),
			(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0*/);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
	case WM_MOUSEWHEEL:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		{
			int zDelta = ((short) HIWORD(wParam));
			POINT cursor, origin;

			origin.x = 0, origin.y = 0;
			ClientToScreen(hWnd, &origin);
			(void) GetCursorPos(&cursor);
			if (zDelta > (WHEEL_DELTA >> 1)) {
				widget.threed.deltaAngle.phi =
					-widget.threed.angle.phi;
				widget.threed.angle.phi =
					(widget.threed.angle.phi +
					DELTADEGREES) % NUM_DEGREES;
				widget.threed.deltaAngle.phi +=
					widget.threed.angle.phi;
				(void) InvalidateRect(hWnd, NULL, TRUE);
			} else if (zDelta < -(WHEEL_DELTA >> 1)) {
				widget.threed.deltaAngle.phi =
					-widget.threed.angle.phi;
				widget.threed.angle.phi = (NUM_DEGREES +
					widget.threed.angle.phi -
					DELTADEGREES) % NUM_DEGREES;
				widget.threed.deltaAngle.phi +=
					widget.threed.angle.phi;
				(void) InvalidateRect(hWnd, NULL, TRUE);
			}
		}
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#endif
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ACTION_EXIT:
			destroyThreeD(brush);
			break;
		case ACTION_TETRA:
		case ACTION_HEXA:
		case ACTION_OCTA:
		case ACTION_DODECA:
		case ACTION_ICOSA:
		case ACTION_RHOMBIC:
		case ACTION_TRIACONTA:
		case ACTION_TRUNC:
		case ACTION_IRREG:
		case ACTION_TETRADECA:
		case ACTION_STAR:
		case ACTION_GREAT_DODECA:
		case ACTION_SMALL_STEL_DODECA:
		case ACTION_GREAT_STEL_DODECA:
		case ACTION_GREAT_ICOSA:
		case ACTION_PYRAMID:
		case ACTION_CUBES:
		case ACTION_F16:
			hMenu = GetMenu(hWnd);
			(void) CheckMenuItem(hMenu,
				(unsigned int) widget.threed.object + ACTION_TETRA,
				MF_UNCHECKED);
			(void) CheckMenuItem(hMenu,
				LOWORD(wParam), MF_CHECKED);
			setobjectThreeD(&widget, LOWORD(wParam) - ACTION_TETRA);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_HIDE:
			hideThreeD(&widget);
			break;
		case ACTION_SURFACE:
			surfaceThreeD(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_OBJECT:
			objectThreeD(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DISTXI:
			if (widget.threed.distance.x <= MAX_DISTANCE -
					DELTADISTANCE)
				widget.threed.distance.x += DELTADISTANCE;
			(void) SetScrollPos(hWnd, SB_HORZ,
				widget.threed.distance.x, TRUE);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DISTXD:
			if (widget.threed.distance.x >= MINDISTANCE +
					DELTADISTANCE)
				widget.threed.distance.x -= DELTADISTANCE;
			(void) SetScrollPos(hWnd, SB_HORZ,
				widget.threed.distance.x, TRUE);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DISTYI:
			if (widget.threed.distance.y <= MAX_DISTANCE -
					DELTADISTANCE)
				widget.threed.distance.y += DELTADISTANCE;
			(void) SetScrollPos(hWnd, SB_VERT,
				widget.threed.distance.y, TRUE);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DISTYD:
			if (widget.threed.distance.y >= MINDISTANCE +
					DELTADISTANCE)
				widget.threed.distance.y -= DELTADISTANCE;
			(void) SetScrollPos(hWnd, SB_VERT,
				widget.threed.distance.y, TRUE);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DEPTHZI:
			if (widget.threed.distance.z <= MAX_DEPTH - DELTADEPTH)
				widget.threed.distance.z += DELTADEPTH;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DEPTHZD:
			if (widget.threed.distance.z >= MIN_DEPTH + DELTADEPTH)
				widget.threed.distance.z -= DELTADEPTH;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_THETAI:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.angle.theta = (widget.threed.angle.theta +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_THETAD:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.angle.theta = (NUM_DEGREES +
				widget.threed.angle.theta -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_PHII:
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.phi = (widget.threed.angle.phi +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_PHID:
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.phi = (NUM_DEGREES +
				widget.threed.angle.phi -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_PSII:
			widget.threed.deltaAngle.psi =
				-widget.threed.angle.psi;
			widget.threed.angle.psi = (widget.threed.angle.psi +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.psi +=
				widget.threed.angle.psi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_PSID:
			widget.threed.deltaAngle.psi =
				-widget.threed.angle.psi;
			widget.threed.angle.psi = (NUM_DEGREES +
				widget.threed.angle.psi -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.psi +=
				widget.threed.angle.psi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DIAG0:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.theta = (NUM_DEGREES +
				widget.threed.angle.theta -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.angle.phi = (widget.threed.angle.phi +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DIAG1:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.theta = (NUM_DEGREES +
				widget.threed.angle.theta -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.angle.phi = (NUM_DEGREES +
				widget.threed.angle.phi -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DIAG2:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.theta = (widget.threed.angle.theta +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.angle.phi = (NUM_DEGREES +
				widget.threed.angle.phi -
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DIAG3:
			widget.threed.deltaAngle.theta =
				-widget.threed.angle.theta;
			widget.threed.deltaAngle.phi =
				-widget.threed.angle.phi;
			widget.threed.angle.theta = (widget.threed.angle.theta +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.angle.phi = (widget.threed.angle.phi +
				DELTADEGREES) % NUM_DEGREES;
			widget.threed.deltaAngle.theta +=
				widget.threed.angle.theta;
			widget.threed.deltaAngle.phi +=
				widget.threed.angle.phi;
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SPEED:
			speedThreeD(&widget);
			break;
		case ACTION_SLOW:
			slowThreeD(&widget);
			break;
		case ACTION_SOUND:
			soundThreeD(&widget);
			break;
		case ACTION_DESCRIPTION:
			(void) MessageBox(hWnd, descriptionHelp,
				"Description", MB_OK | MB_ICONQUESTION);
			break;
		case ACTION_FEATURES:
			(void) MessageBox(hWnd, featuresHelp,
				"Features", MB_OK | MB_ICONEXCLAMATION);
			break;
		case ACTION_REFERENCES:
			(void) MessageBox(hWnd, referencesHelp,
				"References", MB_OK | MB_ICONINFORMATION);
			break;
		case ACTION_ABOUT:
			(void) DialogBox(widget.core.hInstance,
				"About", hWnd, (DLGPROC) about);
			break;
		}
		break;
	case WM_HSCROLL:
		if (wParam == SB_THUMBTRACK)
			break;
		hPos = GetScrollPos(hWnd, SB_HORZ);
		switch (wParam) {
		case SB_TOP:
			hPos = MAX_DISTANCE;
			break;
		case SB_BOTTOM:
			hPos = MINDISTANCE;
			break;
		case SB_LINEUP:
		case SB_PAGEUP:
			hPos -= DELTADISTANCE;
			break;
		case SB_PAGEDOWN:
		case SB_LINEDOWN:
			hPos += DELTADISTANCE;
			break;
		case SB_THUMBPOSITION:
			hPos = LOWORD(lParam);
			break;
		}
		if (hPos < MINDISTANCE)
			hPos = MINDISTANCE;
		else if (hPos > MAX_DISTANCE)
			hPos = MAX_DISTANCE;
		(void) SetScrollPos(hWnd, SB_HORZ, hPos, TRUE);
		widget.threed.distance.x = hPos;
		(void) InvalidateRect(hWnd, NULL, TRUE);
		break;
	case WM_VSCROLL:
		if (wParam == SB_THUMBTRACK)
			break;
		vPos = GetScrollPos(hWnd, SB_VERT);
		switch (wParam) {
		case SB_TOP:
			vPos = MINDISTANCE;
			break;
		case SB_BOTTOM:
			vPos = MAX_DISTANCE;
			break;
		case SB_LINEUP:
		case SB_PAGEUP:
			vPos -= DELTADISTANCE;
			break;
		case SB_PAGEDOWN:
		case SB_LINEDOWN:
			vPos += DELTADISTANCE;
			break;
		case SB_THUMBPOSITION:
			vPos = LOWORD(lParam);
			break;
		}
		if (vPos < MINDISTANCE)
			vPos = MINDISTANCE;
		else if (vPos > MAX_DISTANCE)
			vPos = MAX_DISTANCE;
		(void) SetScrollPos(hWnd, SB_VERT, vPos, TRUE);
		widget.threed.distance.y = -vPos;
		(void) InvalidateRect(hWnd, NULL, TRUE);
		break;
	default:
		return (DefWindowProc(hWnd, message, wParam, lParam));
	}
	return FALSE;
}

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
		int numCmdShow)
{
	HWND hWnd;
	MSG msg;
	WNDCLASS wc;
	HACCEL hAccel;

	if (!hPrevInstance) {
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = WindowProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = hInstance;
		wc.hIcon = LoadIcon(hInstance, TITLE);
		wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
		wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
		wc.lpszMenuName = TITLE;
		wc.lpszClassName = TITLE;
		if (!RegisterClass(&wc))
			return FALSE;
	}
	widget.core.hInstance = hInstance;
	hWnd = CreateWindow(
		TITLE,
		TITLE,
		WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		HWND_DESKTOP,
		(HMENU) NULL,
		hInstance,
		(XtPointer) NULL);
	if (!hWnd)
		return FALSE;
	(void) SetScrollRange(hWnd, SB_HORZ, MINDISTANCE, MAX_DISTANCE, TRUE);
	(void) SetScrollRange(hWnd, SB_VERT, MINDISTANCE, MAX_DISTANCE, TRUE);
	(void) SetScrollPos(hWnd, SB_HORZ, widget.threed.distance.x, TRUE);
	(void) SetScrollPos(hWnd, SB_VERT, widget.threed.distance.y, TRUE);
	hAccel = (HACCEL) LoadAccelerators(hInstance, TITLE);
	(void) ShowWindow(hWnd, numCmdShow);
	(void) UpdateWindow(hWnd);
	while (GetMessage(&msg, (HWND) NULL, 0, 0))
		if (!TranslateAccelerator(hWnd, hAccel, &msg)) {
			(void) TranslateMessage(&msg);
			(void) DispatchMessage(&msg);
		}
	return (int) msg.wParam;
}

#else

#include "xwin.h"
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>
#ifdef HAVE_MOTIF
#include <Xm/PanedW.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>
#include <Xm/LabelG.h>
#include <Xm/MessageB.h>
#include <Xm/PushBG.h>
#include <Xm/CascadeB.h>
#ifdef USE_SPIN
#include <Xm/SSpinB.h>
#else
#include <Xm/Scale.h>
#endif
#include <Xm/ToggleB.h>
#include <Xm/List.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#define PRINT_MESSAGE(b) printState(message, b)
#else
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#include "Threed.h"
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "16x16/threed.xpm"
#include "22x22/threed.xpm"
#include "24x24/threed.xpm"
#include "32x32/threed.xpm"
#include "48x48/threed.xpm"
#include "64x64/threed.xpm"
#else
#include "pixmaps/16x16/threed.xpm"
#include "pixmaps/22x22/threed.xpm"
#include "pixmaps/24x24/threed.xpm"
#include "pixmaps/32x32/threed.xpm"
#include "pixmaps/48x48/threed.xpm"
#include "pixmaps/64x64/threed.xpm"
#endif
#define RESIZE_XPM(s) ((char **) (((s)<=32)?\
(((s)<=22)?(((s)<=16)?threed_16x16:threed_22x22):\
(((s)<=24)?threed_24x24:threed_32x32)):\
(((s)<=48)?threed_48x48:threed_64x64)))
#endif
#include "pixmaps/64x64/threed.xbm"
#define DEFINE_XBM (char *) threed_64x64_bits, threed_64x64_width, threed_64x64_height

#define FILE_NAME_LENGTH 1024
#define TITLE_FILE_LENGTH (FILE_NAME_LENGTH+8)
#define TITLE_LENGTH 2048

#ifdef HAVE_MOTIF
#define SCROLL_SIZE 30		/* A page */
static Widget xDist, yDist, zDist, thetaAng, phiAng, psiAng;
static Widget surfaceSwitch, objectList;
static Widget descriptionDialog, featuresDialog;
static Widget sysnopsisDialog, referencesDialog, aboutDialog;
static char *progDsp;
static Arg args[10];
#else
static char titleDsp[TITLE_LENGTH] = "";
#endif
static Pixmap pixmap = None;
static Widget topLevel, threed;

static void
usage(char *programName)
{
	unsigned int i;

	(void) fprintf(stderr, "usage: %s\n", programName);
	for (i = 0; i < strlen(synopsisHelp); i++) {
		if (i == 0 || synopsisHelp[i - 1] == '\n')
			(void) fprintf(stderr, "\t");
		(void) fprintf(stderr, "%c", (synopsisHelp[i]));
	}
	(void) fprintf(stderr, "\n");
	exit(1);
}

static XrmOptionDescRec options[] = {
	{(char *) "-foreground", (char *) "*threed.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-fg", (char *) "*threed.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-background", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-bg", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-frame", (char *) "*threed.frameColor", XrmoptionSepArg, NULL},
	{(char *) "-white", (char *) "*threed.whiteBrush", XrmoptionSepArg, NULL},
	{(char *) "-ltgray", (char *) "*threed.ltgrayBrush", XrmoptionSepArg, NULL},
	{(char *) "-gray", (char *) "*threed.grayBrush", XrmoptionSepArg, NULL},
	{(char *) "-dkgray", (char *) "*threed.dkgrayBrush", XrmoptionSepArg, NULL},
	{(char *) "-black", (char *) "*threed.blackBrush", XrmoptionSepArg, NULL},
	{(char *) "-another", (char *) "*threed.anotherBrush", XrmoptionSepArg, NULL},
	{(char *) "-border", (char *) "*threed.surfaceBorder", XrmoptionSepArg, NULL},
	{(char *) "-bd", (char *) "*threed.surfaceBorder", XrmoptionSepArg, NULL},
	{(char *) "-delay", (char *) "*threed.delay", XrmoptionSepArg, NULL},
	{(char *) "-sound", (char *) "*threed.sound", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosound", (char *) "*threed.sound", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-bumpSound", (char *) "*threed.bumpSound", XrmoptionSepArg, NULL},
	{(char *) "-moveSound", (char *) "*threed.moveSound", XrmoptionSepArg, NULL},
	{(char *) "-surface", (char *) "*threed.surface", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosurface", (char *) "*threed.surface", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-object", (char *) "*threed.object", XrmoptionSepArg, (char *) "0"},
	{(char *) "-x", (char *) "*threed.distance.x", XrmoptionSepArg, (char *) "0.0"},
	{(char *) "-y", (char *) "*threed.distance.y", XrmoptionSepArg, (char *) "0.0"},
	{(char *) "-z", (char *) "*threed.distance.z", XrmoptionSepArg, (char *) "50.0"},
	{(char *) "-theta", (char *) "*threed.angle.theta", XrmoptionSepArg, (char *) "0.0"},
	{(char *) "-phi", (char *) "*threed.angle.phi", XrmoptionSepArg, (char *) "0.0"},
	{(char *) "-psi", (char *) "*threed.angle.psi", XrmoptionSepArg, (char *) "0.0"},
	{(char *) "-version", (char *) "*threed.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

#ifndef HAVE_MOTIF
static void printState(Widget w, char *name, Boolean surface, int object,
		IntPoint3D distance, IntAngle3D angle)
{
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_LENGTH,
		"%s %d %s:x %d:y %d:z %d:theta %d:phi %d:psi %d", name,
		object, (surface) ? "surface" : "wire",
		distance.x, distance.y, distance.z,
		angle.theta, angle.phi, angle.psi);
#else
	(void)  sprintf(titleDsp,
		"%s %d %s:x %d:y %d:z %d:theta %d:phi %d:psi %d", name,
		object, (surface) ? "surface" : "wire",
		distance.x, distance.y, distance.z,
		angle.theta, angle.phi, angle.psi);
#endif
	XtVaSetValues(w, XtNtitle, titleDsp, NULL);
}
#endif

static void initialize(Widget w)
{
	IntPoint3D distance;
	IntAngle3D angle;
	int object;
	Boolean surface;
	Boolean versionOnly;
	char *name;
#ifndef HAVE_MOTIF
	char **list;
	int number, i;
#endif

	XtVaGetValues(w,
		XtNxDistance, &(distance.x),
		XtNyDistance, &(distance.y),
		XtNzDistance, &(distance.z),
		XtNthetaDegrees, &(angle.theta),
		XtNphiDegrees, &(angle.phi),
		XtNpsiDegrees, &(angle.psi),
		XtNobject, &object,
		XtNsurface, &surface,
		XtNobjectName, &name,
#ifndef HAVE_MOTIF
		XtNobjectNumber, &number,
		XtNobjectList, &list,
#endif
		XtNversionOnly, &versionOnly, NULL);
	if (versionOnly) {
		(void) printf("%s\n", aboutHelp);
		exit(0);
	}
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	XtVaSetValues(xDist, XmNposition, distance.x, NULL);
	XtVaSetValues(yDist, XmNposition, distance.y, NULL);
	XtVaSetValues(zDist, XmNposition, distance.z, NULL);
	XtVaSetValues(thetaAng, XmNposition, angle.theta, NULL);
	XtVaSetValues(phiAng, XmNposition, angle.phi, NULL);
	XtVaSetValues(psiAng, XmNposition, angle.psi, NULL);
#else
	XmScaleSetValue(xDist, distance.x);
	XmScaleSetValue(yDist, distance.y);
	XmScaleSetValue(zDist, distance.z);
	XmScaleSetValue(thetaAng, angle.theta);
	XmScaleSetValue(phiAng, angle.phi);
	XmScaleSetValue(psiAng, angle.psi);
#endif
	XmToggleButtonSetState(surfaceSwitch, surface, True);
#else
	printState(XtParent(w), name, surface, object, distance, angle);
	(void) printf("Objects:\n");
	for (i = 0; i < number; i++)
		(void) printf("\t%s\n", list[i]);
#endif
}

#ifdef HAVE_MOTIF
static void makePosVisible(Widget list_w, int item_no)
{
	int top, visible;

	XtVaGetValues(list_w,
		XmNtopItemPosition, &top,
		XmNvisibleItemCount, &visible,
		NULL);
	if (item_no < top) {
		XmListSetPos(list_w, item_no);
	} else if (item_no >= top + visible) {
		XmListSetBottomPos(list_w, item_no);
	}
}
#endif

static void callbackThreeD(Widget w, caddr_t clientData,
		threedCallbackStruct *callData)
{
	Boolean surface;
	IntPoint3D distance;
	IntAngle3D angle;
	int object, numObjects;
	char *name;

	XtVaGetValues(w,
		XtNobject, &object,
		XtNsurface, &surface,
		XtNxDistance, &(distance.x),
		XtNyDistance, &(distance.y),
		XtNzDistance, &(distance.z),
		XtNthetaDegrees, &(angle.theta),
		XtNphiDegrees, &(angle.phi),
		XtNpsiDegrees, &(angle.psi),
		XtNobjectNumber, &numObjects,
		XtNobjectName, &name,
		NULL);
	switch (callData->reason) {
	case ACTION_HIDE:
		(void) XIconifyWindow(XtDisplay(topLevel), XtWindow(topLevel),
			XScreenNumberOfScreen(XtScreen(topLevel)));
		break;
	case ACTION_SURFACE:
		surface = !surface;
		XtVaSetValues(w, XtNsurface, surface, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(surfaceSwitch, surface, True);
#endif
		break;
	case ACTION_OBJECT:
		if (numObjects > 0)
			object = (object + 1) % numObjects;
		XtVaSetValues(w, XtNobject, object, NULL);
		XtVaGetValues(w,
			XtNobjectName, &name,
			NULL);
#ifdef HAVE_MOTIF
		makePosVisible(objectList, object + 1);
		XmListSelectPos(objectList, object + 1, True);
#endif
		break;
	}
#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	XtVaSetValues(xDist, XmNposition, distance.x, NULL);
	XtVaSetValues(yDist, XmNposition, distance.y, NULL);
	XtVaSetValues(zDist, XmNposition, distance.z, NULL);
	XtVaSetValues(thetaAng, XmNposition, angle.theta, NULL);
	XtVaSetValues(phiAng, XmNposition, angle.phi, NULL);
	XtVaSetValues(psiAng, XmNposition, angle.psi, NULL);
#else
	XmScaleSetValue(xDist, distance.x);
	XmScaleSetValue(yDist, distance.y);
	XmScaleSetValue(zDist, distance.z);
	XmScaleSetValue(thetaAng, angle.theta);
	XmScaleSetValue(phiAng, angle.phi);
	XmScaleSetValue(psiAng, angle.psi);
#endif
#else
	printState(XtParent(w), name, surface, object, distance, angle);
#endif
}

#ifdef HAVE_MOTIF
static void
xDistChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNxDistance, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNxDistance, value,
			NULL);
}

static void
yDistChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNyDistance, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNyDistance, value,
			NULL);
}

static void
zDistChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNzDistance, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNzDistance, value,
			NULL);
}

static void
thetaChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNthetaDegrees, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNthetaDegrees, value,
			NULL);
}

static void
phiChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNphiDegrees, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNphiDegrees, value,
			NULL);
}


static void
psiChanger(Widget w, XtPointer clientData,
#ifdef USE_SPIN
		XmSpinBoxCallbackStruct *cbs
#else
		XmScaleCallbackStruct *cbs
#endif
		)
{
	int value, old;

#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
	XtVaGetValues(threed,
		XtNpsiDegrees, &old,
		NULL);
	if (old != value)
		XtVaSetValues(threed,
			XtNpsiDegrees, value,
			NULL);
}


static void
surfaceToggle(Widget w, XtPointer clientData, XmToggleButtonCallbackStruct *cbs)
{
	XtVaSetValues(threed,
		XtNsurface, cbs->set,
		NULL);
}

static void
objectResponse(Widget listItem, XtPointer clientData, XmListCallbackStruct *cbs)
{
	char	*name;
	int object = cbs->item_position - 1, old;

	XtVaGetValues(threed,
		XtNobject, &old,
		NULL);
	if (old != object)
		XtVaSetValues(threed,
			XtNobject, object,
			NULL);
	XtVaGetValues(threed,
		XtNobjectName, &name,
		NULL);
}

#if 0
static void
createMenu(Widget *menu, Widget rowCol, int init)
{
	XmString choice, tetra, hexa, octa, dodeca, icosa,
	XmString rhombic, triaconta, star, pyramid, cubes, f16;

	choice = XmStringCreateSimple("Object:");
	tetra = XmStringCreateSimple("Tetrahedron");
	hexa = XmStringCreateSimple("Hexa");
	octa = XmStringCreateSimple("Octahedron");
	dodeca = XmStringCreateSimple("Dodecahedron");
	icosa = XmStringCreateSimple("Icosahedron");
	cubes = XmStringCreateSimple("Cubes");
	f16 = XmStringCreateSimple("F16");
	*menu = XmVaCreateSimpleOptionMenu(rowCol, "modeMenu",
		choice, 'M', init, object_response,
		XmVaPUSHBUTTON, tetra, 'T', NULL, NULL,
		XmVaPUSHBUTTON, hexa, 'H', NULL, NULL,
		XmVaPUSHBUTTON, octa, 'O', NULL, NULL,
		XmVaPUSHBUTTON, dodeca, 'D', NULL, NULL,
		XmVaPUSHBUTTON, icosa, 'I', NULL, NULL,
		XmVaPUSHBUTTON, rhombic, 'R', NULL, NULL,
		XmVaPUSHBUTTON, triaconta, 'N', NULL, NULL,
		XmVaPUSHBUTTON, star, 't', NULL, NULL,
		XmVaPUSHBUTTON, pyramid, 'P', NULL, NULL,
		XmVaPUSHBUTTON, cubes, 'C', NULL, NULL,
		XmVaPUSHBUTTON, f16, 'F', NULL, NULL,
		NULL);
	XmStringFree(choice);
	XmStringFree(tetra);
	XmStringFree(hexa);
	XmStringFree(octa);
	XmStringFree(dodeca);
	XmStringFree(icosa);
	XmStringFree(rhombic);
	XmStringFree(triaconta);
	XmStringFree(star);
	XmStringFree(pyramid);
	XmStringFree(cubes);
	XmStringFree(f16);
	XtManageChild(*menu);
}
#endif

static void
createList(Widget *rowCol, char **list, int init, int num)
{
	XmString label;
	XmStringTable table;
	int i;
	table = (XmStringTable) XtMalloc (num * sizeof (XmString *));
	for (i = 0; i < num; i++)
		table[i] = XmStringCreateSimple(list[i]);

	label = XmStringCreateSimple((char *) "Object list:");
	(void) XtVaCreateManagedWidget("listLabel",
		xmLabelWidgetClass, *rowCol,
		XmNlabelString, label,
		NULL);
	XmStringFree(label);
	objectList = XmCreateScrolledList(*rowCol, (char *) "scrolledList",
		NULL, 0);
	XtVaSetValues(objectList,
		XmNvisibleItemCount, 5,
		XmNitemCount, num,
		XmNitems, table,
		NULL);
	XtManageChild(objectList);
	XtAddCallback(objectList, XmNbrowseSelectionCallback,
		(XtCallbackProc) objectResponse, (XtPointer) NULL);
	XmListSelectPos(objectList, init + 1, True);
	makePosVisible(objectList, init + 1);

	for (i = 0; i < num; i++)
		XmStringFree(table[i]);
	XtFree((char *) table);
}

static Widget
createHelp(Widget w, char *text, char *title)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL, messageString = NULL, buttonString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	messageString = XmStringCreateLtoR(text, charSet);
#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_FILE_LENGTH,
		"%s: %s", progDsp, title);
#else
	(void) sprintf(titleDsp,
		"%s: %s", progDsp, title);
#endif
	titleString = XmStringCreateSimple((char *) titleDsp);
	buttonString = XmStringCreateSimple((char *) "OK");
	XtSetArg(args[0], XmNdialogTitle, titleString);
	XtSetArg(args[1], XmNokLabelString, buttonString);
	XtSetArg(args[2], XmNmessageString, messageString);
	messageBox = XmCreateInformationDialog(w, (char *) "helpBox",
		args, 3);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(buttonString);
	XmStringFree(messageString);
	return messageBox;
}

#if 0
/*http://www.ist.co.uk/motif/books/vol6A/ch-5.fm.html*/
static Widget
createScrollHelp(Widget w, const char *text, char *title)
{
	Widget dialog, pane, scrolledText, form, label, button;
	int n = 0;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL;

#ifdef HAVE_SNPRINTF
	(void) snprintf(titleDsp, TITLE_FILE_LENGTH,
		"%s: %s", progDsp, title);
#else
	(void) sprintf(titleDsp,
		"%s: %s", progDsp, title);
#endif
	dialog = XmCreateMessageDialog(w, titleDsp, NULL, 0);
	titleString = XmStringCreateSimple((char *) titleDsp);
	pane = XtVaCreateWidget("pane", xmPanedWindowWidgetClass, dialog,
		XmNsashWidth, 1, XmNsashHeight, 1, NULL);
	form = XtVaCreateWidget("form", xmFormWidgetClass, pane, NULL);
	label = XtVaCreateManagedWidget("label", xmLabelGadgetClass, form,
		XmNlabelType, XmPIXMAP,
		XmNlabelPixmap, pixmap,
		XmNleftAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_FORM,
		XmNbottomAttachment, XmATTACH_FORM,
		NULL);
	XtSetArg(args[n], XmNdialogTitle, titleString); n++;
	XtSetArg(args[n], XmNscrollVertical, True); n++;
	XtSetArg(args[n], XmNscrollHorizontal, False); n++;
	XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
	XtSetArg(args[n], XmNeditable, False); n++;
	XtSetArg(args[n], XmNcursorPositionVisible, False); n++;
	XtSetArg(args[n], XmNwordWrap, False); n++;
	XtSetArg(args[n], XmNresizeWidth, True); n++;
	XtSetArg(args[n], XmNvalue, text); n++;
	XtSetArg(args[n], XmNrows, SCROLL_SIZE); n++;
	scrolledText = XmCreateScrolledText(form, (char *) "helpText", args, n);
	XtVaSetValues(XtParent(scrolledText),
		XmNleftAttachment, XmATTACH_WIDGET,
		XmNleftWidget, label,
		XmNtopAttachment, XmATTACH_FORM,
		XmNrightAttachment, XmATTACH_FORM,
		NULL);
	XmStringFree(titleString);
	XtManageChild(scrolledText);
	XtManageChild(form);
	XtManageChild(pane);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	return dialog;
}
#endif

static void
fileMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) ((size_t) value);

	if (val == 0)
		exit(0);
}

static void
controlsMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) ((size_t) value);

	XtVaSetValues(threed, XtNmenu, val + ACTION_SPEED, NULL);
}


static void
helpMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) ((size_t) value);

	switch (val) {
	case 0:
		XtManageChild(descriptionDialog);
		break;
	case 1:
		XtManageChild(featuresDialog);
		break;
	case 2:
		XtManageChild(sysnopsisDialog);
		break;
	case 3:
		XtManageChild(referencesDialog);
		break;
	case 4:
		XtManageChild(aboutDialog);
		break;
	default:
		{
			char *buf;

			intCat(&buf, "helpMenuListener: %d", val);
			XtWarning(buf);
			free(buf);
		}
	}
}
#endif

int main(int argc, char **argv)
{
	int pixmapSize;
#ifdef HAVE_MOTIF
	Widget menuBar, pullDownMenu, widget;
	Widget panel, mainPanel, controlPanel;
	Widget distRowCol, angleRowCol, shapeRowCol;
	Widget changerDistXRowCol, changerDistYRowCol, changerDistZRowCol;
	Widget changerThetaAngRowCol, changerPhiAngRowCol, changerPsiAngRowCol;
	XmString fileString, controlsString;
	XmString quitString;
	XmString speedString, slowString, soundString;
	char **list;
	int object, number;

	progDsp = argv[0];
#endif
	topLevel = XtInitialize(argv[0], "Threed",
		options, XtNumber(options), &argc, argv);
	if (argc != 1)
		usage(argv[0]);

#ifdef HAVE_MOTIF
	panel = XtVaCreateManagedWidget("panel",
		xmPanedWindowWidgetClass, topLevel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	fileString = XmStringCreateSimple((char *) "File");
	controlsString = XmStringCreateSimple((char *) "Controls");
	menuBar = XmVaCreateSimpleMenuBar(panel, (char *) "menuBar",
		XmVaCASCADEBUTTON, fileString, 'F',
		XmVaCASCADEBUTTON, controlsString, 'C',
		NULL);
	XmStringFree(fileString);
	XmStringFree(controlsString);
	quitString = XmStringCreateSimple((char *) "Exit");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "fileMenu",
		0, fileMenuListener,
		XmVaPUSHBUTTON, quitString, 'x', NULL, NULL,
		NULL);
	XmStringFree(quitString);
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
	soundString = XmStringCreateSimple((char *) "Sound @");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "controlsMenu",
		1, controlsMenuListener,
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
		XmVaPUSHBUTTON, soundString, '@', NULL, NULL,
		NULL);
	XmStringFree(speedString);
	XmStringFree(slowString);
	XmStringFree(soundString);
	pullDownMenu = XmCreatePulldownMenu(menuBar,
		(char *) "helpPullDown", NULL, 0);
	widget = XtVaCreateManagedWidget("Help",
		xmCascadeButtonWidgetClass, menuBar,
		XmNsubMenuId, pullDownMenu,
		XmNmnemonic, 'H', NULL); /* mnemonic error on Cygwin */
	XtVaSetValues(menuBar, XmNmenuHelpWidget, widget, NULL);
	widget = XtVaCreateManagedWidget("Description",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'D', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 0);
	widget = XtVaCreateManagedWidget("Features",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'F', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 1);
	widget = XtVaCreateManagedWidget("Synopsis",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'S', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 2);
	widget = XtVaCreateManagedWidget("References",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'R', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 3);
	widget = XtVaCreateManagedWidget("About",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'A', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 4);
	XtManageChild(menuBar);
	mainPanel = XtCreateManagedWidget("mainPanel",
		xmPanedWindowWidgetClass, panel,
		NULL, 0);
	controlPanel = XtVaCreateManagedWidget("controlPanel",
		xmPanedWindowWidgetClass, mainPanel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	distRowCol = XtVaCreateManagedWidget("distRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	changerDistXRowCol = XtVaCreateManagedWidget("changerDistXRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerDistXRowCol, (char *) "    X:", NULL, 0));
	xDist = XtVaCreateManagedWidget("xDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerDistXRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerDistXRowCol,
		XmNminimum, MINDISTANCE,
		XmNmaximum, MAX_DISTANCE,
		XmNvalue, (MINDISTANCE + MAX_DISTANCE) / 2,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "X", 2,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(xDist, XmNvalueChangedCallback,
		(XtCallbackProc) xDistChanger, (XtPointer) NULL);
	changerDistYRowCol = XtVaCreateManagedWidget("changerDistYRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerDistYRowCol, (char *) "  Y:", NULL, 0));
	yDist = XtVaCreateManagedWidget("yDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerDistYRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerDistYRowCol,
		XmNminimum, MINDISTANCE,
		XmNmaximum, MAX_DISTANCE,
		XmNvalue, (MINDISTANCE + MAX_DISTANCE) / 2,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Y", 2,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(yDist, XmNvalueChangedCallback,
		(XtCallbackProc) yDistChanger, (XtPointer) NULL);
	changerDistZRowCol = XtVaCreateManagedWidget("changerDistZRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerDistZRowCol, (char *) "  Z:", NULL, 0));
	zDist = XtVaCreateManagedWidget("zDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerDistZRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerDistZRowCol,
		XmNminimum, MIN_DEPTH,
		XmNmaximum, MAX_DEPTH,
		XmNvalue, (MIN_DEPTH + MAX_DEPTH) / 2,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Z", 2,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(zDist, XmNvalueChangedCallback,
		(XtCallbackProc) zDistChanger, (XtPointer) NULL);
	angleRowCol = XtVaCreateManagedWidget("angleRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	changerThetaAngRowCol = XtVaCreateManagedWidget("changerThetaAngRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerThetaAngRowCol, (char *) "Theta:", NULL, 0));
	thetaAng = XtVaCreateManagedWidget("thetaAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerThetaAngRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerThetaAngRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Theta", 6,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(thetaAng, XmNvalueChangedCallback,
		(XtCallbackProc) thetaChanger, (XtPointer) NULL);
	changerPhiAngRowCol = XtVaCreateManagedWidget("changerPhiAngRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerPhiAngRowCol, (char *) "Phi:", NULL, 0));
	phiAng = XtVaCreateManagedWidget("phiAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerPhiAngRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerPhiAngRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Phi", 4,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(phiAng, XmNvalueChangedCallback,
		(XtCallbackProc) phiChanger, (XtPointer) NULL);
	changerPsiAngRowCol = XtVaCreateManagedWidget("changerPsiAngRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT,
		NULL);
	XtManageChild(XmCreateLabel(changerPsiAngRowCol, (char *) "Psi:", NULL, 0));
	psiAng = XtVaCreateManagedWidget("psiAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerPsiAngRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MINDISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MINDISTANCE + MAX_DISTANCE) / 2,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerPsiAngRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Psi", 4,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL,
		NULL);
	XtAddCallback(psiAng, XmNvalueChangedCallback,
		(XtCallbackProc) psiChanger, (XtPointer) NULL);
	threed = XtCreateManagedWidget("threed",
		threedWidgetClass, panel, NULL, 0);
#else
	threed = XtCreateManagedWidget("threed",
		threedWidgetClass, topLevel,
		NULL, 0);
#endif
	XtVaGetValues(threed,
#ifdef HAVE_MOTIF
		XtNobject, &object,
		XtNobjectNumber, &number,
		XtNobjectList, &list,
#endif
		XtNpixmapSize, &pixmapSize, NULL);
#ifdef HAVE_XPM
	{
		XpmAttributes xpmAttributes;
		XpmColorSymbol transparentColor[1] = {{NULL,
			(char *) "none", 0 }};
		Pixel bg;

		xpmAttributes.valuemask = XpmColorSymbols | XpmCloseness;
		xpmAttributes.colorsymbols = transparentColor;
		xpmAttributes.numsymbols = 1;
		xpmAttributes.closeness = 40000;
		XtVaGetValues(topLevel, XtNbackground, &bg, NULL);
		transparentColor[0].pixel = bg;
		(void) XpmCreatePixmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			RESIZE_XPM(pixmapSize), &pixmap, NULL,
			&xpmAttributes);
	}
	if (pixmap == (Pixmap) NULL)
#endif
		pixmap = XCreateBitmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			DEFINE_XBM);
	XtVaSetValues(topLevel,
#ifdef HAVE_MOTIF
		XmNkeyboardFocusPolicy, XmPOINTER, /* not XmEXPLICIT */
#else
		XtNinput, True,
#endif
		XtNiconPixmap, pixmap, NULL);
	XtAddCallback(threed, XtNselectCallback,
		(XtCallbackProc) callbackThreeD, (XtPointer) NULL);
#ifdef HAVE_MOTIF
	descriptionDialog = createHelp(menuBar, (char *) descriptionHelp,
		(char *) "Description");
	featuresDialog = createHelp(menuBar, (char *) featuresHelp,
		(char *) "Features");
	sysnopsisDialog = createHelp(menuBar, (char *) synopsisHelp,
		(char *) "Synopsis");
	referencesDialog = createHelp(menuBar, (char *) referencesHelp,
		(char *) "References");
	aboutDialog = createHelp(menuBar, (char *) aboutHelp,
		(char *) "About");
	shapeRowCol = XtVaCreateManagedWidget("shapeRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNorientation, XmHORIZONTAL,
		NULL);
	/*createMenu(&objectMenu, shapeRowCol, object);*/
	createList(&shapeRowCol, list, object, number);
	surfaceSwitch = XtVaCreateManagedWidget("Surface",
		xmToggleButtonWidgetClass, shapeRowCol,
		XmNset, DEFAULTSURFACE, NULL);
	XtAddCallback(surfaceSwitch, XmNvalueChangedCallback,
		(XtCallbackProc) surfaceToggle, (XtPointer) NULL);
#endif
	initialize(threed);
	XtRealizeWidget(topLevel);
	XGrabButton(XtDisplay(threed), (unsigned int) AnyButton, AnyModifier,
		XtWindow(threed), TRUE, (unsigned int) (ButtonPressMask |
		ButtonMotionMask | ButtonReleaseMask),
		GrabModeAsync, GrabModeAsync, XtWindow(threed),
		XCreateFontCursor(XtDisplay(threed), XC_hand2));
	XtMainLoop();

#ifdef VMS
	return 1;
#else
	return 0;
#endif
}
#endif
