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

/* 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}]\n"
"[-[ltlt|dkdk]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(HAVE_ATHENA) || defined(HAVE_XAW3D) || 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 \"B\" or \"b\" keys to change object.\n"
"\n"
"Press \"S\" or \"s\" keys to change surface.\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_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;
	case ACTION_SURFACE:
		w->threed.surface = !w->threed.surface;
		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_CUBOCT:
		case ACTION_SQUARE_ANTIPRISM:
		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_OBJECT:
			objectThreeD(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SURFACE:
			surfaceThreeD(&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 >= MIN_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_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 >= MIN_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_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 = MIN_DISTANCE;
			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 < MIN_DISTANCE)
			hPos = MIN_DISTANCE;
		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 = MIN_DISTANCE;
			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 < MIN_DISTANCE)
			vPos = MIN_DISTANCE;
		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, MIN_DISTANCE, MAX_DISTANCE, TRUE);
	(void) SetScrollRange(hWnd, SB_VERT, MIN_DISTANCE, 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>
#elif defined(HAVE_XAW3D)
#include <X11/Xaw3d/Form.h>
#include <X11/Xaw3d/Paned.h>
#include <X11/Xaw3d/Label.h>
#include <X11/Xaw3d/Toggle.h>
#include <X11/Xaw3d/Box.h>
#include <X11/Xaw3d/Scrollbar.h>
#include <X11/Xaw3d/SimpleMenu.h>
#include <X11/Xaw3d/MenuButton.h>
#include <X11/Xaw3d/SmeBSB.h>
#include <X11/Xaw3d/SmeLine.h>
#include <X11/Xaw3d/Command.h>
#include <X11/Xaw3d/Dialog.h>
#include <X11/Xaw3d/AsciiText.h>
#define HAVE_ATHENA 1
#elif defined(HAVE_ATHENA)
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Scrollbar.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/SmeLine.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Dialog.h>
#include <X11/Xaw/AsciiText.h>
#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 Arg args[10];
#else
#ifdef HAVE_ATHENA
#define SCROLL_SIZE 100
static Atom wmDeleteWindow = None;
static Widget xDistSliderLabel, yDistSliderLabel, zDistSliderLabel;
static Widget thetaSliderLabel, phiSliderLabel, psiSliderLabel;
static Widget listLabel;
static char *fileTypes[] =
{
	(char *) "Exit"
};
#define numFileTypes (sizeof(fileTypes)/sizeof(fileTypes[0]))
static char *controlTypes[] =
{
	(char *) "Sound @  "
};
#define numControlTypes (sizeof(controlTypes)/sizeof(controlTypes[0]))
static char *helpTypes[] =
{
	(char *) "Description...",
	(char *) "Features...",
	(char *) "Synopsis...",
	(char *) "References...",
	(char *) "About..."
};
#define numHelpTypes (sizeof(helpTypes)/sizeof(helpTypes[0]))
#include "pixmaps/check.xbm"
#endif
static char titleDsp[TITLE_LENGTH] = "";
#endif
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
#define PRINT_MESSAGE(b) printState(message, b)
static Widget xDist, yDist, zDist, thetaAng, phiAng, psiAng;
static Widget objectList, surfaceSwitch;
static char *xLabel = "    X:";
static char *yLabel = "  Y:";
static char *zLabel = "  Z:";
static char *thetaLabel = "Theta:";
static char *phiLabel = "Phi:";
static char *psiLabel = "Psi:";
static Widget soundMenuItem;
static Widget descriptionDialog, featuresDialog;
static Widget synopsisDialog, referencesDialog, aboutDialog;
static char *progDsp;
#else
#define PRINT_MESSAGE(b) XtWarning(b)
#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 *) "-ltltgray", (char *) "*threed.ltltgrayBrush", XrmoptionSepArg, NULL},
	{(char *) "-gray", (char *) "*threed.grayBrush", XrmoptionSepArg, NULL},
	{(char *) "-dkgray", (char *) "*threed.dkgrayBrush", XrmoptionSepArg, NULL},
	{(char *) "-dkdkgray", (char *) "*threed.dkdkgrayBrush", 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 *) "-objectName", (char *) "*threed.name", XrmoptionSepArg, (char *) NULL},
	{(char *) "-object", (char *) "*threed.object", XrmoptionSepArg, (char *) NULL},
	{(char *) "-surface", (char *) "*threed.surface", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosurface", (char *) "*threed.surface", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-x", (char *) "*threed.xDistance", XrmoptionSepArg, (char *) NULL},
	{(char *) "-y", (char *) "*threed.yDistance", XrmoptionSepArg, (char *) NULL},
	{(char *) "-z", (char *) "*threed.zDistance", XrmoptionSepArg, (char *) NULL},
	{(char *) "-theta", (char *) "*threed.thetaDegrees", XrmoptionSepArg, (char *) NULL},
	{(char *) "-phi", (char *) "*threed.phiDegrees", XrmoptionSepArg, (char *) NULL},
	{(char *) "-psi", (char *) "*threed.psiDegrees", XrmoptionSepArg, (char *) NULL},
	{(char *) "-version", (char *) "*threed.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

#ifdef HAVE_MOTIF
static void
soundToggle(Widget w, XtPointer clientData,
		XmToggleButtonCallbackStruct *cbs)
{
	Boolean value = cbs->set;

	XtVaSetValues(threed,
		XtNsound, value, NULL);
}

static void
updateToggle(Widget menu, Widget *menuItem, Boolean toggle, int number,
		XtCallbackProc callback)
{
	char button[10];

	(void) sprintf(button, "button_%d", number);
	if ((*menuItem = XtNameToWidget(menu, button)) != (Widget) 0) {
		if (toggle)
			XtVaSetValues(*menuItem,
				XmNset, toggle, NULL);
		XtAddCallback(*menuItem, XmNvalueChangedCallback, callback,
			(XtPointer) NULL);
	}
}

#else

static void printState(Widget w, char *name, int object, Boolean surface,
		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

#ifdef HAVE_ATHENA
static void
setLabel(Widget label, char *prefix, int value)
{
	char buff[BUFSIZ];

	(void) sprintf(buff, "%s %3d", prefix, value);
	XtVaSetValues(label,
		XtNlabel, buff, NULL);
}

static void
setScale(Widget slider, Widget label, char *prefix,
		int value, int min, int max)
{
	XawScrollbarSetThumb(slider, ((float) (value - min)) /
		(max - min), 0.0);
	setLabel(label, prefix, value);
}

static void
setScaleCheck(Widget slider, Widget label, char *prefix,
		int value, int min, int max)
{
	float old;
	int oldValue;

	XtVaGetValues(slider,
		XtNtopOfThumb, &old, NULL);
	oldValue = (int) (old * (max - min)) + min;
	if (oldValue == value) /* reduce flashing */
		return;
	setScale(slider, label, prefix, value, min, max);
}

static void createBlank(char **string, int size, char *defaultString,
		int side) {
	int i, defSize = strlen(defaultString) + 1, midSize;
	char * val;

	*string = (char *) malloc(size);
	val = *string;
	if (side == 0) /* middle */
		midSize = (size - defSize) >> 1;
	else /* right justified */
		midSize = size - defSize;
	for (i = 0; i < size - 1; i++) {
		if (i - midSize >= 0 &&
				(i - midSize < defSize - 1))
			val[i] = defaultString[i - midSize];
		else
			val[i] = ' ';
	}
	val[size - 1] = '\0';
}

static int findMaxLength(char **list, int num) {
	int i, temp, max;

	max = 0;
	for (i = 0; i < num; i++) {
		temp = strlen(list[i]);
		if (temp > max)
			max = temp;
	}
	return ++max;
}
#endif

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
static void
concatTitle(char *newTitle, char *title,
		const char *joinString, const char *string, int n)
{
	int stringLen = strlen(string);
	int joinStringLen = strlen(joinString);
	int len = n - stringLen - joinStringLen;

	if (len > 0) {
		(void) strncpy(newTitle, title, len);
		newTitle[len - 1] = '\0';
		(void) strcat(newTitle, joinString);
		(void) strcat(newTitle, string);
	} else {
		(void) strncpy(newTitle, title, n - 1);
		newTitle[n - 1] = '\0';
	}
}

static void replaceAll(char * string, char old, char new)
{
	int i;

	for (i = 0; i < strlen(string); i++)
		if (string[i] == old)
			string[i] = new;
}

static void
surfaceToggle(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
	XmToggleButtonCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
	int state
#endif
	)
{
	XtVaSetValues(threed,
		XtNsurface,
#ifdef HAVE_MOTIF
		cbs->set,
#elif defined(HAVE_ATHENA)
		state,
#endif
		NULL);
}

static void
objectListener(Widget w, XtPointer clientData,
#ifdef HAVE_MOTIF
	XmListCallbackStruct *cbs
#elif defined(HAVE_ATHENA)
	XtPointer callData
#endif
	)
{
	char *name;
	int object, old;

#ifdef HAVE_MOTIF
	object = cbs->item_position - 1;
#elif defined(HAVE_ATHENA)
	object = (size_t) clientData;
#endif
	XtVaGetValues(threed,
		XtNobject, &old, NULL);
	if (old == object)
		return;
	XtVaSetValues(threed,
		XtNobject, object, NULL);
	XtVaGetValues(threed,
		XtNobjectName, &name, NULL);
#ifdef HAVE_ATHENA
	XtVaSetValues(listLabel,
		XtNlabel, name, NULL);
#endif
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	value = ABS(position) * (MAX_DISTANCE - MIN_DISTANCE) /
		SCROLL_SIZE + MIN_DISTANCE;
	if (value < MIN_DISTANCE || value > MAX_DISTANCE) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DISTANCE - MIN_DISTANCE)) + MIN_DISTANCE;
		if (value < MIN_DISTANCE || value > MAX_DISTANCE)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNxDistance, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, xLabel, value, MIN_DISTANCE, MAX_DISTANCE);
#endif
	XtVaSetValues(threed,
		XtNxDistance, value, NULL);
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	position = ABS(position);
	value = position * (MAX_DISTANCE - MIN_DISTANCE) /
		SCROLL_SIZE + MIN_DISTANCE;
	if (value < MIN_DISTANCE || value > MAX_DISTANCE) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DISTANCE - MIN_DISTANCE)) + MIN_DISTANCE;
		if (value < MIN_DISTANCE || value > MAX_DISTANCE)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNyDistance, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, yLabel, value, MIN_DISTANCE, MAX_DISTANCE);
#endif
	XtVaSetValues(threed,
		XtNyDistance, value, NULL);
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	position = ABS(position);
	value = position * (MAX_DEPTH - MIN_DEPTH) /
		SCROLL_SIZE + MIN_DEPTH;
	if (value < MIN_DEPTH || value > MAX_DEPTH) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DEPTH - MIN_DEPTH)) + MIN_DEPTH;
		if (value < MIN_DEPTH || value > MAX_DEPTH)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNzDistance, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, zLabel, value, MIN_DEPTH, MAX_DEPTH);
#endif
	XtVaSetValues(threed,
		XtNzDistance, value, NULL);
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	position = ABS(position);
	value = position * (MAX_DEGREES - MIN_DEGREES) /
		SCROLL_SIZE + MIN_DEGREES;
	if (value < MIN_DEGREES || value > MAX_DEGREES) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DEGREES - MIN_DEGREES)) + MIN_DEGREES;
		if (value < MIN_DEGREES || value > MAX_DEGREES)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNthetaDegrees, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, thetaLabel, value, MIN_DEGREES, MAX_DEGREES);
#endif
	XtVaSetValues(threed,
		XtNthetaDegrees, value, NULL);
}

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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	position = ABS(position);
	value = position * (MAX_DEGREES - MIN_DEGREES) /
		SCROLL_SIZE + MIN_DEGREES;
	if (value < MIN_DEGREES || value > MAX_DEGREES) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DEGREES - MIN_DEGREES)) + MIN_DEGREES;
		if (value < MIN_DEGREES || value > MAX_DEGREES)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNphiDegrees, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, phiLabel, value, MIN_DEGREES, MAX_DEGREES);
#endif
	XtVaSetValues(threed,
		XtNphiDegrees, value, NULL);
}


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

#ifdef HAVE_MOTIF
#ifdef USE_SPIN
	value = cbs->position;
#else
	value = cbs->value;
#endif
#elif defined(HAVE_ATHENA)
	Widget label = (Widget) clientData;
	long position = (size_t) callData; /* left or right button */
	position = ABS(position);
	value = position * (MAX_DEGREES - MIN_DEGREES) /
		SCROLL_SIZE + MIN_DEGREES;
	if (value < MIN_DEGREES || value > MAX_DEGREES) {
		/* middle button */
		value = (int) (*((float *) callData) *
			(MAX_DEGREES - MIN_DEGREES)) + MIN_DEGREES;
		if (value < MIN_DEGREES || value > MAX_DEGREES)
			return;
	}
#endif
	XtVaGetValues(threed,
		XtNpsiDegrees, &old, NULL);
	if (old == value)
		return;
#ifdef HAVE_ATHENA
	setScale(w, label, phiLabel, value, MIN_DEGREES, MAX_DEGREES);
#endif
	XtVaSetValues(threed,
		XtNpsiDegrees, value, NULL);
}

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

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

static void
syncSoundCheck(Widget w)
{
#if HAVE_ATHENA
	static Pixmap checkPixmap = None;
#endif
	Boolean sound;
	XtVaGetValues(threed,
		XtNsound, &sound, NULL);
#ifdef HAVE_MOTIF
	XmToggleButtonSetState(w, sound, False);
#else
	if (checkPixmap == None)
		checkPixmap = XCreateBitmapFromData(XtDisplay(topLevel),
			RootWindow(XtDisplay(topLevel),
			DefaultScreen(XtDisplay(topLevel))),
			(char *)check_bits, check_width, check_height);
	XtVaSetValues(w,
		XtNrightBitmap, ((sound) ? checkPixmap : None), NULL);
#endif
}

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

	XtVaSetValues(threed, XtNmenu, value +
#ifdef SPEED
ACTION_SPEED
#else
ACTION_SOUND
#endif
, NULL);
	if (value == ACTION_SOUND)
		syncSoundCheck(w);
}

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

#ifdef HAVE_ATHENA
	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(topLevel),
				"WM_DELETE_WINDOW", FALSE);
#endif
	switch (value) {
	case 0:
		dialog = descriptionDialog;
		break;
	case 1:
		dialog = featuresDialog;
		break;
	case 2:
		dialog = synopsisDialog;
		break;
	case 3:
		dialog = referencesDialog;
		break;
	case 4:
		dialog = aboutDialog;
		break;
	default:
		{
			char *buf;

			intCat(&buf, "helpMenuListener: %d", value);
			XtWarning(buf);
			free(buf);
			return;
		}
	}
#ifdef HAVE_MOTIF
	XtManageChild(dialog);
#elif defined(HAVE_ATHENA)
	XtPopup(dialog, XtGrabNone);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(dialog), &wmDeleteWindow, 1);
#endif
}
#endif

static void initialize(Widget w)
{
	IntPoint3D distance;
	IntAngle3D angle;
	int object;
	Boolean surface;
	Boolean versionOnly;
	char *name;
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
	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,
#if !defined(HAVE_MOTIF) && !defined(HAVE_ATHENA)
		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);
#elif defined(HAVE_ATHENA)
	if (!surface)
		XawToggleUnsetCurrent(surfaceSwitch);
#else
	printState(XtParent(w), name, object, surface, 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 threeDListener(Widget w, caddr_t clientData,
		threedCallbackStruct *callData)
{
	Boolean surface;
	IntPoint3D distance;
	IntAngle3D angle;
	int object, number;
	char *name;
#ifdef HAVE_ATHENA
	int max;
	char *defaultString;
	char **list;
#endif

	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, &number,
#ifdef HAVE_ATHENA
		XtNobjectList, &list,
#endif
		XtNobjectName, &name, NULL);
	switch (callData->reason) {
	case ACTION_HIDE:
		(void) XIconifyWindow(XtDisplay(topLevel), XtWindow(topLevel),
			XScreenNumberOfScreen(XtScreen(topLevel)));
		break;
	case ACTION_OBJECT:
		if (number > 0)
			object = (object + 1) % number;
		XtVaSetValues(w, XtNobject, object, NULL);
		XtVaGetValues(w,
			XtNobjectName, &name, NULL);
#ifdef HAVE_MOTIF
		makePosVisible(objectList, object + 1);
		XmListSelectPos(objectList, object + 1, True);
#elif defined(HAVE_ATHENA)
		max = findMaxLength((char **) list, number);
		createBlank(&defaultString, max, (char *) list[object], 0);
		XtVaSetValues(listLabel,
			XtNlabel, defaultString, NULL);
		free(defaultString);
#endif
		break;
	case ACTION_SURFACE:
		surface = !surface;
		XtVaSetValues(w, XtNsurface, surface, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(surfaceSwitch, surface, True);
#elif defined(HAVE_ATHENA)
		XtVaSetValues(surfaceSwitch,
			XtNstate, surface, NULL);
#endif
		break;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	case ACTION_SOUND:
		syncSoundCheck(soundMenuItem);
		break;
#endif
	}
#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
#ifdef HAVE_ATHENA
	setScaleCheck(xDist, xDistSliderLabel, xLabel,
		distance.x, MIN_DISTANCE, MAX_DISTANCE);
	setScaleCheck(yDist, yDistSliderLabel, yLabel,
		distance.y, MIN_DISTANCE, MAX_DISTANCE);
	setScaleCheck(zDist, zDistSliderLabel, zLabel,
		distance.z, MIN_DEPTH, MAX_DEPTH);
	setScaleCheck(thetaAng, thetaSliderLabel, thetaLabel,
		angle.theta, MIN_DEGREES, MAX_DEGREES);
	setScaleCheck(phiAng, phiSliderLabel, phiLabel,
		angle.phi, MIN_DEGREES, MAX_DEGREES);
	setScaleCheck(psiAng, psiSliderLabel, psiLabel,
		angle.psi, MIN_DEGREES, MAX_DEGREES);
#endif
	printState(XtParent(w), name, object, surface, distance, angle);
#endif
}

#ifdef HAVE_MOTIF
#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("Hexahedron");
	octa = XmStringCreateSimple("Octahedron");
	dodeca = XmStringCreateSimple("Dodecahedron");
	icosa = XmStringCreateSimple("Icosahedron");
	rhombic = XmStringCreateSimple("Rhombic");
	triaconta = XmStringCreateSimple("Triaconta");
	star = XmStringCreateSimple("Star");
	pyramid = XmStringCreateSimple("Pyramid");
	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++) {
		replaceAll(list[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) objectListener, (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 *title, char *text)
{
	Widget button, messageBox;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL, messageString = NULL, buttonString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	titleString = XmStringCreateSimple((char *) titleDsp);
	buttonString = XmStringCreateSimple((char *) "OK");
	messageString = XmStringCreateLtoR(text, charSet);
	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 *title, char *text)
{
	Widget dialog, pane, scrolledText, form, label, button;
	int n = 0;
	char titleDsp[TITLE_FILE_LENGTH];
	XmString titleString = NULL;

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	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

#elif defined(HAVE_ATHENA)

static void deleteWindowProc(Widget w, XEvent *event,
		String *pars, Cardinal *nPars)
{
	exit(0);
}

static void closePopupPanel(Widget w)
{
	XtPopdown(w);
}

static void
createXDistSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNxDistance, &value, NULL);
	xDist = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	xDistSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, xDist, NULL);
	setScale(xDist, xDistSliderLabel, xLabel, value,
		MIN_DISTANCE, MAX_DISTANCE);
	XtAddCallback(xDist,
		XtNscrollProc, xDistChangeListener,
		(XtPointer) xDistSliderLabel);
	XtAddCallback(xDist,
		XtNjumpProc, xDistChangeListener,
		(XtPointer) xDistSliderLabel);
}

static void
createYDistSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNyDistance, &value, NULL);
	yDist = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	yDistSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, yDist, NULL);
	setScale(yDist, yDistSliderLabel, yLabel, value,
		MIN_DISTANCE, MAX_DISTANCE);
	XtAddCallback(yDist,
		XtNscrollProc, yDistChangeListener,
		(XtPointer) yDistSliderLabel);
	XtAddCallback(yDist,
		XtNjumpProc, yDistChangeListener,
		(XtPointer) yDistSliderLabel);
}

static void
createZDistSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNzDistance, &value, NULL);
	zDist = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	zDistSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, zDist, NULL);
	setScale(zDist, zDistSliderLabel, zLabel, value,
		MIN_DEPTH, MAX_DEPTH);
	XtAddCallback(zDist,
		XtNscrollProc, zDistChangeListener,
		(XtPointer) zDistSliderLabel);
	XtAddCallback(zDist,
		XtNjumpProc, zDistChangeListener,
		(XtPointer) zDistSliderLabel);
}

static void
createThetaSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNthetaDegrees, &value, NULL);
	thetaAng = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	thetaSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, thetaAng, NULL);
	setScale(thetaAng, thetaSliderLabel, thetaLabel, value,
		MIN_DEGREES, MAX_DEGREES);
	XtAddCallback(thetaAng,
		XtNscrollProc, thetaChangeListener,
		(XtPointer) thetaSliderLabel);
	XtAddCallback(thetaAng,
		XtNjumpProc, thetaChangeListener,
		(XtPointer) thetaSliderLabel);
}

static void
createPhiSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNphiDegrees, &value, NULL);
	phiAng = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	phiSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, phiAng, NULL);
	setScale(phiAng, phiSliderLabel, phiLabel, value,
		MIN_DEGREES, MAX_DEGREES);
	XtAddCallback(phiAng,
		XtNscrollProc, phiChangeListener,
		(XtPointer) phiSliderLabel);
	XtAddCallback(phiAng,
		XtNjumpProc, phiChangeListener,
		(XtPointer) phiSliderLabel);
}

static void
createPsiSlider(Widget form)
{
	int value;

	XtVaGetValues(threed,
		XtNpsiDegrees, &value, NULL);
	psiAng = XtVaCreateManagedWidget("scrollbar",
		scrollbarWidgetClass, form,
		XtNorientation, XtorientHorizontal,
		XtNlength, SCROLL_SIZE, NULL);
	psiSliderLabel = XtVaCreateManagedWidget("label",
		labelWidgetClass, form,
		XtNfromHoriz, psiAng, NULL);
	setScale(psiAng, psiSliderLabel, psiLabel, value,
		MIN_DEGREES, MAX_DEGREES);
	XtAddCallback(psiAng,
		XtNscrollProc, psiChangeListener,
		(XtPointer) psiSliderLabel);
	XtAddCallback(psiAng,
		XtNjumpProc, psiChangeListener,
		(XtPointer) psiSliderLabel);
}

static void
createList(Widget *form, char **list, int init, int num)
{
	Widget w;
	int i, max;
	char *defaultString;

	max = findMaxLength(list, num);
	createBlank(&defaultString, max, (char *) list[init], 0);
	listLabel = XtVaCreateManagedWidget(defaultString,
		menuButtonWidgetClass, *form,
		/*XtNwidth, 202,*/
		NULL);
	free(defaultString);
	objectList = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, listLabel, NULL);
	for (i = 0; i < num; i++) {
		replaceAll(list[i], '_', ' ');
		w = XtVaCreateManagedWidget(list[i],
			smeBSBObjectClass, objectList, NULL);
		XtAddCallback(w, XtNcallback,
			(XtCallbackProc) objectListener,
			(XtPointer) (size_t) i);
	}
}

static void
okListener(Widget w, XtPointer clientData, XtPointer callData)
{
	XtPopdown(XtParent(XtParent(w)));
}

static void
createHelp(Widget *dialogShell, char *title, char *text)
{
	Widget dialog, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("dialog",
		dialogWidgetClass, *dialogShell,
		XtNlabel, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog, NULL);
	XtAddCallback(button, XtNcallback,
		okListener, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(*dialogShell), &wmDeleteWindow, 1);
}

static void
createScrollHelp(Widget *dialogShell, char *title, char *text)
{
	Widget dialog, scroll, button;
	char titleDsp[TITLE_FILE_LENGTH];

	concatTitle(titleDsp, progDsp, ": ", title, TITLE_FILE_LENGTH);
	*dialogShell = XtCreatePopupShell(titleDsp,
		transientShellWidgetClass, topLevel, NULL, 0);
	dialog = XtVaCreateManagedWidget("text",
		formWidgetClass, *dialogShell, NULL);
	scroll = XtVaCreateManagedWidget("text",
		asciiTextWidgetClass, dialog,
		XtNwidth, 105 * 8,
		XtNheight, 256,
		XtNscrollVertical, XawtextScrollAlways,
		XtNstring, text, NULL);
	button = XtVaCreateManagedWidget("OK",
		commandWidgetClass, dialog,
		XtNfromVert, scroll, NULL);
	XtAddCallback(button, XtNcallback,
		okListener, dialog);
	XtRealizeWidget(*dialogShell);
	XSetWMProtocols(XtDisplay(topLevel),
		XtWindow(*dialogShell), &wmDeleteWindow, 1);
}
#endif

int
main(int argc, char **argv)
{
	int pixmapSize;
	XtAppContext appCon;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	Widget mainPanel, controlPanel, menuBar;
	Widget distRowCol, angleRowCol, shapeRowCol;
	Widget changerXDistRowCol, changerYDistRowCol, changerZDistRowCol;
	Widget changerThetaRowCol, changerPhiRowCol, changerPsiRowCol;
	Widget controlsMenu;
	char **list;
	int object, number;
#endif
#ifdef HAVE_MOTIF
	Widget pullDownMenu, widget;
	Widget panel;
	XmString fileString, controlsString;
	XmString quitString;
#ifdef SPEED
	XmString speedString, slowString;
#endif
	XmString soundString;

#elif defined(HAVE_ATHENA)
	XtActionsRec actions[] = {
		{"DeleteWindowProc", deleteWindowProc},
		{"ClosePopupPanel", (XtActionProc)closePopupPanel}
	};
	String fallbackResources[] = {
		"?.translations: #override <Message>WM_PROTOCOLS: DeleteWindowProc()",
		"?.TransientShell.translations: #override <Message>WM_PROTOCOLS: ClosePopupPanel()",
		NULL
	};
	Widget listMenu;
	Widget w, fileMenu, helpMenu;
	Widget fileLabel, controlsLabel, helpLabel;
	int i;
#endif

#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	progDsp = strrchr(argv[0], '/');
	if (progDsp != NULL)
		progDsp++;
	if (progDsp == NULL)
		progDsp = argv[0];
#endif
	topLevel = XtVaAppInitialize(NULL, "Threed",
		options, XtNumber(options), &argc, argv,
#ifdef HAVE_ATHENA
		fallbackResources,
#else
		NULL,
#endif
		NULL);
	appCon = XtWidgetToApplicationContext(topLevel);
	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);
#ifdef SPEED
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
#endif
	soundString = XmStringCreateSimple((char *) "Sound @");
	controlsMenu = XmVaCreateSimplePulldownMenu(menuBar, (char *) "controlsMenu",
		1, controlsMenuListener,
#ifdef SPEED
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
#endif
		XmVaTOGGLEBUTTON, soundString, '@', NULL, NULL, NULL);
#ifdef SPEED
	XmStringFree(speedString);
	XmStringFree(slowString);
#endif
	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 = XtVaCreateManagedWidget("mainPanel",
		xmPanedWindowWidgetClass, panel, NULL);
	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);
	changerXDistRowCol = XtVaCreateManagedWidget("changerXDistRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerXDistRowCol, xLabel, NULL, 0));
	xDist = XtVaCreateManagedWidget("xDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerXDistRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerXDistRowCol,
		XmNminimum, MIN_DISTANCE,
		XmNmaximum, MAX_DISTANCE,
		XmNvalue, (MIN_DISTANCE + MAX_DISTANCE) / 2,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "X", 2,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(xDist, XmNvalueChangedCallback,
		(XtCallbackProc) xDistChangeListener, (XtPointer) NULL);
	changerYDistRowCol = XtVaCreateManagedWidget("changerYDistRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerYDistRowCol, yLabel, NULL, 0));
	yDist = XtVaCreateManagedWidget("yDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerYDistRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerYDistRowCol,
		XmNminimum, MIN_DISTANCE,
		XmNmaximum, MAX_DISTANCE,
		XmNvalue, (MIN_DISTANCE + MAX_DISTANCE) / 2,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Y", 2,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(yDist, XmNvalueChangedCallback,
		(XtCallbackProc) yDistChangeListener, (XtPointer) NULL);
	changerZDistRowCol = XtVaCreateManagedWidget("changerZDistRowCol",
		xmRowColumnWidgetClass, distRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerZDistRowCol, zLabel, NULL, 0));
	zDist = XtVaCreateManagedWidget("zDist",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerZDistRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerZDistRowCol,
		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) zDistChangeListener, (XtPointer) NULL);
	angleRowCol = XtVaCreateManagedWidget("angleRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	changerThetaRowCol = XtVaCreateManagedWidget("changerThetaRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerThetaRowCol, thetaLabel, NULL, 0));
	thetaAng = XtVaCreateManagedWidget("thetaAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerThetaRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerThetaRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Theta", 6,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(thetaAng, XmNvalueChangedCallback,
		(XtCallbackProc) thetaChangeListener, (XtPointer) NULL);
	changerPhiRowCol = XtVaCreateManagedWidget("changerPhiRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerPhiRowCol, phiLabel, NULL, 0));
	phiAng = XtVaCreateManagedWidget("phiAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerPhiRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerPhiRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Phi", 4,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(phiAng, XmNvalueChangedCallback,
		(XtCallbackProc) phiChangeListener, (XtPointer) NULL);
	changerPsiRowCol = XtVaCreateManagedWidget("changerPsiRowCol",
		xmRowColumnWidgetClass, angleRowCol,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_TIGHT, NULL);
	XtManageChild(XmCreateLabel(changerPsiRowCol, psiLabel, NULL, 0));
	psiAng = XtVaCreateManagedWidget("psiAng",
#ifdef USE_SPIN
		xmSimpleSpinBoxWidgetClass, changerPsiRowCol,
		XmNspinBoxChildType, XmNUMERIC,
		XmNminimumValue, MIN_DISTANCE,
		XmNmaximumValue, MAX_DISTANCE,
		XmNposition, (MIN_DISTANCE + MAX_DISTANCE) / 2,
		XmNwrap, FALSE,
		XmNeditable, FALSE,
		XmNincrementValue, 1,
		XmNcolumns, 2,
#else
		xmScaleWidgetClass, changerPsiRowCol,
		XmNminimum, MIN_DEGREES,
		XmNmaximum, MAX_DEGREES,
		XmNvalue, MIN_DEGREES,
#endif
		/*XtVaTypedArg, XmNtitleString, XmRString, "Psi", 4,*/
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(psiAng, XmNvalueChangedCallback,
		(XtCallbackProc) psiChangeListener, (XtPointer) NULL);
	threed = XtVaCreateManagedWidget("threed",
		threedWidgetClass, panel, NULL);
#elif defined(HAVE_ATHENA)
	XtAppAddActions(appCon, actions, XtNumber(actions));
	createHelp(&descriptionDialog, (char *) "Description",
		(char *) descriptionHelp);
	createScrollHelp(&featuresDialog, (char *) "Features",
		(char *) featuresHelp);
	createHelp(&synopsisDialog, (char *) "Synopsis",
		(char *) synopsisHelp);
	createHelp(&referencesDialog, (char *) "References",
		(char *) referencesHelp);
	createHelp(&aboutDialog, (char *) "About",
		(char *) aboutHelp);
	mainPanel = XtVaCreateManagedWidget("form",
		panedWidgetClass, topLevel, NULL);
	menuBar = XtVaCreateManagedWidget("MenuBar",
		formWidgetClass, mainPanel,
		XtNborderWidth, 1, NULL);
	fileLabel = XtVaCreateManagedWidget("File",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0, NULL);
	fileMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, fileLabel, NULL);
	for (i = 0; i < numFileTypes; i++) {
		w = XtVaCreateManagedWidget(fileTypes[i],
			smeBSBObjectClass, fileMenu, NULL);
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) fileMenuListener,
			(XtPointer) (size_t) i);
	}
	controlsLabel = XtVaCreateManagedWidget("Controls",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0,
		XtNfromHoriz, fileLabel, NULL);
	controlsMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, controlsLabel,
		XtNrightMargin, 8, NULL); /* for check */
	for (i = 0; i < numControlTypes; i++) {
		w = XtVaCreateManagedWidget(controlTypes[i],
			smeBSBObjectClass, controlsMenu, NULL);
		if (i == numControlTypes - 1)
			soundMenuItem = w;
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) controlsMenuListener,
			(XtPointer) (size_t) i);
	}
	helpLabel = XtVaCreateManagedWidget("Help",
		menuButtonWidgetClass, menuBar,
		XtNborderWidth, 0,
		XtNfromHoriz, controlsLabel, NULL);
	helpMenu = XtVaCreatePopupShell("menu",
		simpleMenuWidgetClass, helpLabel, NULL);
	for (i = 0; i < numHelpTypes; i++) {
		w = XtVaCreateManagedWidget(helpTypes[i],
			smeBSBObjectClass, helpMenu, NULL);
		XtAddCallback(w,
			XtNcallback, (XtCallbackProc) helpMenuListener,
			(XtPointer) (size_t) i);
	}
	controlPanel = XtVaCreateManagedWidget("controlPanel",
		formWidgetClass, mainPanel,
		XtNborderWidth, 0,
		XtNfromVert, menuBar, NULL);
	distRowCol = XtVaCreateManagedWidget("distRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0, NULL);
	changerXDistRowCol = XtVaCreateManagedWidget("changerXDistRowCol",
		formWidgetClass, distRowCol,
		XtNborderWidth, 0, NULL);
	changerYDistRowCol = XtVaCreateManagedWidget("changerYDistRowCol",
		formWidgetClass, distRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, changerXDistRowCol, NULL);
	changerZDistRowCol = XtVaCreateManagedWidget("changerZDistRowCol",
		formWidgetClass, distRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, changerYDistRowCol, NULL);
	angleRowCol = XtVaCreateManagedWidget("angleRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, distRowCol, NULL);
	changerThetaRowCol = XtVaCreateManagedWidget("changerThetaRowCol",
		formWidgetClass, angleRowCol,
		XtNborderWidth, 0, NULL);
	changerPhiRowCol = XtVaCreateManagedWidget("changerPhiRowCol",
		formWidgetClass, angleRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, changerThetaRowCol, NULL);
	changerPsiRowCol = XtVaCreateManagedWidget("changerPsiRowCol",
		formWidgetClass, angleRowCol,
		XtNborderWidth, 0,
		XtNfromHoriz, changerPhiRowCol, NULL);
	shapeRowCol = XtVaCreateManagedWidget("shapeRowCol",
		formWidgetClass, controlPanel,
		XtNborderWidth, 0,
		XtNfromVert, angleRowCol, NULL);
	listMenu = XtVaCreateManagedWidget("listMenu",
		formWidgetClass, shapeRowCol,
		XtNborderWidth, 0,
		XtNvertDistance, 0, NULL);
	threed = XtVaCreateManagedWidget("threed",
		threedWidgetClass, mainPanel,
		XtNfromVert, controlPanel, NULL);
	createXDistSlider(changerXDistRowCol);
	createYDistSlider(changerYDistRowCol);
	createZDistSlider(changerZDistRowCol);
	createThetaSlider(changerThetaRowCol);
	createPhiSlider(changerPhiRowCol);
	createPsiSlider(changerPsiRowCol);
	XtVaGetValues(threed,
		XtNobject, &object,
		XtNobjectNumber, &number,
		XtNobjectList, &list, NULL);
	createList(&listMenu, list, object, number);
	surfaceSwitch = XtVaCreateManagedWidget("surface",
		toggleWidgetClass, shapeRowCol,
		XtNfromHoriz, listMenu,
		XtNstate, DEFAULT_SURFACE, NULL);
	XtAddCallback(surfaceSwitch, XtNcallback,
		(XtCallbackProc) surfaceToggle, (XtPointer) NULL);
	XtRealizeWidget(topLevel);
	if (wmDeleteWindow == None)
		wmDeleteWindow = XInternAtom(XtDisplay(topLevel),
			"WM_DELETE_WINDOW", FALSE);
	XSetWMProtocols(XtDisplay(topLevel), XtWindow(topLevel),
		&wmDeleteWindow, 1);
#else

	threed = XtVaCreateManagedWidget("threed",
		threedWidgetClass, topLevel, NULL);
#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) threeDListener, (XtPointer) NULL);
#ifdef HAVE_MOTIF
	updateToggle(controlsMenu, &soundMenuItem,
		False,
#ifdef EXTRA
		2,
#else
		0,
#endif
		(XtCallbackProc) soundToggle);
	descriptionDialog = createHelp(menuBar,
		(char *) "Description", (char *) descriptionHelp);
	featuresDialog = createHelp(menuBar,
		(char *) "Features", (char *) featuresHelp);
	synopsisDialog = createHelp(menuBar,
		(char *) "Synopsis", (char *) synopsisHelp);
	referencesDialog = createHelp(menuBar,
		(char *) "References", (char *) referencesHelp);
	aboutDialog = createHelp(menuBar,
		(char *) "About", (char *) aboutHelp);
	shapeRowCol = XtVaCreateManagedWidget("shapeRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNorientation, XmHORIZONTAL, NULL);
	/*createMenu(&objectMenu, shapeRowCol, object);*/
	createList(&shapeRowCol, list, object, number);
	surfaceSwitch = XtVaCreateManagedWidget("Surface",
		xmToggleButtonWidgetClass, shapeRowCol,
		XmNset, DEFAULT_SURFACE,
		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));
	XtAppMainLoop(appCon);

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