/* <fpd.c> 8dec04
**
** Graphics display of FAH molecule for Linux
**
** Copyright (C) 2002-2004 Richard P. Howell IV.  This is free
** software; you can distribute it and/or modify it under the terms of
** the GNU General Public License.  There is no warranty whatsoever.
**
** To compile:
**  cc -o fpd -L/usr/X11R6/lib fpd.c -lGL -lGLU -lX11 -lXext -lm -lc
**   on some systems it might also need -I/usr/X11R6/include
*/

static char version[] = "fpd v1.2";
static char copyright[] = "(C) 2004 Richard P. Howell IV";

/* Define SYSTYPE as needed.  This only affects the queue data display.
** 0 - Linux
** 1 - Windows NT, 2K, ME, XP
** 2 - OS X
*/

#ifndef SYSTYPE
# define SYSTYPE	0
#endif
#ifndef NOGLU
# define NOGLU		0			/* 1 if no GLU functions available */
#endif
#ifndef NOPMAP
# define NOPMAP		0			/* 1 if OpenGL can't render into Pixmap */
#endif
#ifndef DLIST
# define DLIST		0			/* 1 if OpenGL should create a display list */
#endif
#ifndef DEFQVER
# define DEFQVER	324			/* Default client version number if file looks bad */
#endif
#ifndef MAXQVER
# define MAXQVER	502			/* Maximum queue version number allowed */
#endif

#include <GL/glx.h>
#if !NOGLU
# include <GL/glu.h>
#endif
#include <GL/gl.h>				/* This is probably already included from <glx.h> */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>

#define IZOOM		3.0			/* Initial zoom factor (Z distance) */
#define FOV			45.0		/* Normal (not stereo) field of view */
#define ZOOMF		1.25		/* Zoom in/out factor */
#define DEPF		1.3			/* Stereo depth more/less factor */
#define SDEP		9.0			/* Initial stereo depth */
#define SSEP		0.5			/* Stereo separation (fraction of FOV) */
#define DQUAL		5000.0		/* Medium display quality */
#define BSBSIZ		0.20		/* Ball size in Ball and Stick model */

#define XYZFILE		"work/current.xyz"
#define QFILE		"queue.dat"
#define TIME_OFS	946684800U	/* Linux epoch is 1970, FAH is 2000 */
#define MAXBONDS	8			/* Maximum bonds on one atom */
#define MAXATOMS	100000		/* Maximum atoms in molecule */

#define FONT		"10x20"		/* Default font name */
#define M_LW		2			/* Line width separating menu buttons */

/* Element numbers, when they are indexed */

#define E_Q			0			/* Unknown */
#define E_H			1			/* Hydrogen */
#define E_C			2			/* Carbon */
#define E_N			3			/* Nitrogen */
#define E_O			4			/* Oxygen */
#define E_P			5			/* Phosphorus */
#define E_S			6			/* Sulfur */

#define ETYPES		"?HCNOPS"	/* Known elements (in order) */

#ifndef FALSE
# define FALSE		0
# define TRUE		1
#endif

typedef int bool;

#define rankof(x) (sizeof(x)/sizeof(*x))

struct bnd
{	int			toward;			/* Atom bonded to */
	float		len;			/* Bond length (/2) */
	float		theta;			/* Z-axis rotation */
	float		phi;			/* Y-axis rotation */
};

struct atm
{	short		element;		/* Element */
	short		flags;			/* Flags */
	float		x;				/* X coordinate */
	float		y;				/* Y coordinate */
	float		z;				/* Z coordinate */
	ulong		bpat;			/* Bond pattern */
	struct bnd	achc;			/* Alpha carbon peptide bond toward carbonyl */
	struct bnd	acha;			/* Alpha carbon peptide bond toward imino */
	struct bnd	bond[MAXBONDS];	/* Bond descriptions */
};

/* Atom flag values */

#define AF_H	0x0001		/* Hydrogen */
#define AF_H2O	0x0002		/* Water */
#define AF_UREA	0x0004		/* Urea */
#define AF_CA	0x0008		/* Alpha Carbon */
#define AF_BB	0x0010		/* Backbone */
#define AF_CN	0x0020		/* Carbon in carbonyl of peptide bond or amide */
#define AF_NNH2	0x0040		/* Nitrogen in amino */
#define AF_DL	0x4000		/* Atom is in display list */

struct adist
{	float		r;
	struct atm *pa;
};

/* Atomic radii, bond lengths, and similar things are in Angstroms.
** The display sizes of the atoms are, to some extent, a fudge.
*/

struct atmprop					/* Atom properties (in ETYPES order) */
{	float		d_radius;		/* Display radius of this element type */
	float		b_radius;		/* Bonding radius (max) of this element type */
	float		color[4];		/* Color (R G B A) */
} aprops[] =
 {	{ 1.50, 1.00, { 0.80, 0.10, 0.80, 1.00 } },	/* ? purple */
	{ 1.17, 0.37, { 0.50, 0.50, 0.50, 1.00 } },	/* H light gray */
	{ 1.75, 0.77, { 0.30, 0.30, 0.30, 1.00 } },	/* C dark gray */
	{ 1.55, 0.77, { 0.20, 0.20, 0.80, 1.00 } },	/* N blue */
	{ 1.40, 0.74, { 0.80, 0.10, 0.20, 1.00 } },	/* O red */
	{ 2.04, 1.11, { 0.10, 0.60, 0.10, 1.00 } },	/* P green */
	{ 1.84, 1.06, { 0.60, 0.60, 0.15, 1.00 } }	/* S yellow */
 };

struct atmprop acprops[] =		/* Pseudo-atom properties in alpha-carbon chain */
 {	{ 0.80, 0.00, { 0.60, 0.65, 0.30, 1.00 } },	/* Normal gold */
	{ 0.80, 0.00, { 0.15, 0.65, 0.60, 1.00 } },	/* Amino end turquoise */
	{ 0.80, 0.00, { 0.70, 0.40, 0.40, 1.00 } },	/* Carboxyl end pink */
 };

#define MAX_BOND_LEN		2.4
#define CH_BOND_LEN			1.095
#define CH_BOND_ID			21
#define BOND_RADIUS			0.07
#define ALPHA_BOND_RADIUS	0.48
#define LARGEST_ATOM		2.10

/* Menu definitions [0-19] */

#define MB_SF	0			/* SF */
#define MB_BS	1			/* BS */
#define MB_WF	2			/* WF */
#define MB_AT	3			/* AT */
#define MB_ST	4			/* STEREO */
#define MB_RE	5			/* REDRAW */
#define MB_HE	6			/* HELP */
#define MB_QU	7			/* QUIT */
#define MB_ZI	8			/* ZOOM IN */
#define MB_ZO	9			/* ZOOM OUT */
#define MB_NV	10			/* NEW VIEW */
#define MB_OP	11			/* OPTIONS */
#define MB_QI	12			/* QI */
#define MB_N	13			/* Total number of menu buttons */

/* Button state values */

#define BS_Q	-1			/* Not a queryable button */
#define BS_F	0			/* Button in "normal" (off) state */
#define BS_P	1			/* Button in "pending on" state */
#define BS_N	2			/* Button in "pending off" state */
#define BS_T	3			/* Button in "toggled" (on) state */

/* Menu structure flag values */

#define BF_E	0x000F		/* Special effects */
#define BF_B	0x0010		/* Region is a normal button */
#define BF_I	0x0020		/* Region is a button inactive in toggled state */
#define BF_R	0x0040		/* Region starts a new line */
#define BF_C	0x0080		/* Region starts a new line with button clearance */
#define BF_S	0x0100		/* Region starts a new line with extra space */
#define BF_Z	0x0200		/* Region should be cleared before text is drawn */
#define BF_A	0x4000		/* Button is active even if toggled */
#define BF_T	0x8000		/* Button in toggled state */

struct mbtn
{	short		flags;		/* Flags */
	short		row;		/* Row, starting at 0 */
	char *		text;		/* Label text */
	int			len;		/* String length of text */
} menu[] =
 {	{ BF_I + 1, 0, "SF" },			/* Space Filling model */
	{ BF_I + 2, 0, "BS" },			/* Ball and Stick model */
	{ BF_I + 3, 0, "WF" },			/* Wire Frame model */
	{ BF_I + 4, 0, "AT" },			/* Alpha Carbon Trace model */
	{ BF_B + 0, 0, "STEREO" },		/* Redraw as stereo display (toggle) */
	{ BF_B + 0, 0, "REDRAW" },		/* Redraw the molecule */
	{ BF_B + 0, 0, "HELP" },		/* Print help text (toggle) */
	{ BF_B + 0, 0, "QUIT" },		/* Exit the program */
	{ BF_B + 0, 1, "ZOOM IN" },		/* Display larger */
	{ BF_B + 0, 1, "ZOOM OUT" },	/* Display smaller */
	{ BF_B + 0, 1, "NEW VIEW" },	/* Redraw the molecule with a new random view */
	{ BF_B + 0, 1, "OPTIONS" },		/* Set processing options */
	{ BF_B + 0, 1, "QI" },			/* Show queue information */
	{ 0 }
 };

/* Structure for help and options screens */

struct sbtn
{	short		flags;		/* Flags */
	short		id;			/* Button ID */
	char *		text;		/* Label text */
	int			len;		/* String length of text */
};

/* Help screen definitions [20-29] */

#define HB_SF	20			/* SF */
#define HB_BS	21			/* BS */
#define HB_WF	22			/* WF */
#define HB_AT	23			/* AT */
#define HB_ST	24			/* STEREO */
#define HB_ZI	25			/* ZOOM IN */
#define HB_ZO	26			/* ZOOM OUT */

struct sbtn helpscreen[] =
 {	{ BF_I,        HB_SF, "SF" },			/* Space Filling model */
	{ BF_B, -1, "       Space Filling model" },
	{ BF_C | BF_I, HB_BS, "BS" },			/* Ball and Stick model */
	{ BF_B, -1, "       Ball and Stick model" },
	{ BF_C | BF_I, HB_WF, "WF" },			/* Wire Frame model */
	{ BF_B, -1, "       Wire Frame model" },
	{ BF_C | BF_I, HB_AT, "AT" },			/* Alpha Carbon Trace model */
	{ BF_B, -1, "       Alpha Carbon Trace model" },
	{ BF_C, HB_ST, "STEREO" },				/* Redraw as stereo display */
	{ BF_B, -1, "   Right and left views (toggle)" },
	{ BF_C, -1, "REDRAW    Redraw with pending changes" },
	{ BF_R, -1, "HELP      Show this text (toggle)" },
	{ BF_R, -1, "QUIT      Exit the program" },
	{ BF_C, HB_ZI, "ZOOM IN" },
	{ BF_B, -1, "  Closer, by 25%" },
	{ BF_C, HB_ZO, "ZOOM OUT" },
	{ BF_B, -1, " Further away, by 25%" },
	{ BF_R, -1, "NEW VIEW  Randomize settings and redraw" },
	{ BF_R, -1, "OPTIONS   Set options (toggle)" },
	{ BF_R, -1, "QI        Show queue info (toggle)" },
	{ BF_S, -1, "Left mouse button rotates molecule.  Drag" },
	{ BF_R, -1, "through center to rotate out of plane of" },
	{ BF_R, -1, "image; nearer edge rotates more in plane." },
	{ BF_R, -1, "Right button moves origin." },
	{ BF_S, -1, "\377a" },
	{ 0 }
 };

/* Options screen definitions [30-59] */

#define OB_RI	30			/* Redraw immediately when change selected */
#define OB_RR	31			/* Redraw only on REDRAW or NEW VIEW */
#define OB_NC	32			/* No change after rotation */
#define OB_SF	33			/* SF after rotation */
#define OB_BS	34			/* BS after rotation */
#define OB_WF	35			/* WF after rotation */
#define OB_AT	36			/* AT after rotation */
#define OB_LR	37			/* Stereo [L R] */
#define OB_RL	38			/* Stereo [R L] */
#define OB_SD	39			/* Stereo depth slider (#1) */
#define OB_DQ	40			/* Display quality slider (#2) */
#define OB_SS	41			/* Ball size in BS model slider (#3) */
#define OB_VF	42			/* NEW VIEW can select SF model */
#define OB_VB	43			/* NEW VIEW can select BS model */
#define OB_VW	44			/* NEW VIEW can select WF model */
#define OB_VA	45			/* NEW VIEW can select AT model */
#define OB_VS	46			/* NEW VIEW can select Stereo */
#define OB_VR	47			/* NEW VIEW can change Rotation */
#define OB_VZ	48			/* NEW VIEW can reset Zoom */
#define OB_VO	49			/* NEW VIEW can reset Origin */
#define OB_BO	50			/* Display backbone only */
#define OB_IH	51			/* Don't display hydrogen atoms */
#define OB_IW	52			/* Don't display water molecules */
#define OB_IU	53			/* Don't display urea molecules */
#define OB_BF	54			/* Use bond information in XYZ file */
#define OB_BP	55			/* Derive bonds from XYZ positions */
#define OB_RS	56			/* Remember settings */

struct sbtn optsscreen[] =
 {	{ BF_B, -1, "When to redraw screen" },
	{ BF_C, -1, " " },
	{ BF_I, OB_RI, "On any new selection in menu bar" },
	{ BF_C, -1, " " },
	{ BF_I, OB_RR, "Only on REDRAW, NEW VIEW, or mouse drag" },
	{ BF_S, -1, "Display model after rotation" },
	{ BF_C, -1, " " },
	{ BF_I, OB_NC, "What it was" },
	{ BF_B, -1, "  " },
	{ BF_I, OB_SF, "SF" },
	{ BF_B, -1, "  " },
	{ BF_I, OB_BS, "BS" },
	{ BF_B, -1, "  " },
	{ BF_I, OB_WF, "WF" },
	{ BF_B, -1, "  " },
	{ BF_I, OB_AT, "AT" },
	{ BF_S, -1, "Selections affected by NEW VIEW" },
	{ BF_C, -1, " " },
	{ BF_B, OB_VF, "SF" },
	{ BF_B, OB_VB, "BS" },
	{ BF_B, OB_VW, "WF" },
	{ BF_B, OB_VA, "AT" },
	{ BF_B, OB_VS, "Stereo" },
	{ BF_B, OB_VR, "Rotation" },
	{ BF_B, OB_VZ, "Zoom" },
	{ BF_B, OB_VO, "Origin" },
	{ BF_S, -1, "Stereo mode       " },
	{ BF_I, OB_LR, "[L R]" },
	{ BF_B, -1, "   " },
	{ BF_I, OB_RL, "[R L]" },
	{ BF_S, -1, "Stereo depth        " },
	{ BF_B, OB_SD, "\377b1  W = 11" },
	{ BF_S, -1, "Display quality     " },
	{ BF_B, OB_DQ, "\377b2  W = 11" },
	{ BF_S, -1, "BS model ball size  " },
	{ BF_B, OB_SS, "\377b3  W = 11" },
	{ BF_S, -1, "Don't show  " },
	{ BF_B, OB_BO, "Non-backbone" },
	{ BF_B, -1, "" },
	{ BF_B, OB_IH, "H" },
	{ BF_B, -1, "" },
	{ BF_B, OB_IW, "Water" },
	{ BF_B, -1, "" },
	{ BF_B, OB_IU, "Urea" },
	{ BF_S, -1, "Bond info from   " },
	{ BF_I, OB_BF, "File data" },
	{ BF_B, -1, " " },
	{ BF_I, OB_BP, "Distance" },
#ifdef NOTYET
	{ BF_S, OB_RS, "Remember settings" },
	{ BF_S, -1, "" },
#endif
	{ BF_S, -1, "---------------------------------------" },
	{ BF_R, -1, version },
	{ BF_B, -1, "" },
	{ BF_B, -1, copyright },
	{ 0 }
 };

/* Queue information screen */

#define QB_QD	60			/* First of 20 buttons allocated for queue data */

struct sbtn qiscreen[32];	/* Built dynamically */

#define MAXB	30			/* Largest number of buttons on any screen */

/* Clickable button structure */

struct btn
{	int			id;			/* Button ID number, -1 ends list */
	short		left;		/* X position of left */
	short		right;		/* X position of right */
	short		top;		/* Y position of top */
	short		bottom;		/* Y position of bottom */
};

/* Slider widget structure
**
** val = k1 + k2 * (pos - delta) / (fscw * len - 2 * delta)
**  where "pos" is the position of the slider and "delta" is
**  the width of the slider end stop, both in pixels,
**  It means that the range of val is from k1 to (k1 + k2).
*/

struct sldr
{	int			id;			/* Button ID number */
	float		k1;			/* Translation parameter 1 */
	float		k2;			/* Translation parameter 2 */
	int			nds;		/* Numeric display style */
	int			len;		/* Total length of slider, in characters */
	float		val;		/* Position, in translated units */
} sldtab[] =
 {	{ OB_SD, -10.0, 20.0, 1 },	/* Stereo depth (smaller makes display flatter) */
	{ OB_DQ, -10.0, 20.0, 1 },	/* Display quality (larger is nicer but slower) */
	{ OB_SS, 0.0, 1.0, 2 }		/* BS model ball size (fraction of SF size) */
 };

/* Current option state */

#define OF_DM	0x00000001	/* Switch to default model on rotation */
#define OF_RL	0x00000002	/* Stereo in [R L] mode */
#define OF_RR	0x00000004	/* Redraw only on REDRAW or NEW VIEW */
#define OF_VF	0x00000008	/* NEW VIEW can select SF model */
#define OF_VB	0x00000010	/* NEW VIEW can select BS model */
#define OF_VW	0x00000020	/* NEW VIEW can select WF model */
#define OF_VA	0x00000040	/* NEW VIEW can select AT model */
#define OF_VS	0x00000080	/* NEW VIEW can select Stereo */
#define OF_VR	0x00000100	/* NEW VIEW can change Rotation */
#define OF_VZ	0x00000200	/* NEW VIEW can reset Zoom */
#define OF_VO	0x00000400	/* NEW VIEW can reset Origin */
#define OF_BO	0x00000800	/* Display backbone only */
#define OF_IH	0x00001000	/* Don't display hydrogen atoms */
#define OF_IW	0x00002000	/* Don't display water molecules */
#define OF_IU	0x00004000	/* Don't display urea molecules */
#define OF_BI	0x00008000	/* Derive bond info from XYZ location only */
#define OF_QT	0x01000000	/* Queue file type explicitly specified */
#define OF_XF	0x02000000	/* XYZ file explicitly specified */

int oflags;			/* Selections */
int rmodel;			/* Default model after rotation, if selected */

/* Global variables */

int atoms;			/* Total number of atoms in "atom" table */
struct atm *atom;	/* Pointer to allocated storage for all XYZ file data */
struct adist *patom;	/* Pointer to auxiliary pointer table for sorting */
float scale;		/* Scale factor for normalizing molecule size */
float fov;			/* Current field of view (degrees) */
float bond_radius;	/* Scaled bond thickness (displayed as cylinder) */
char pname[32];		/* Name of protein */
char *argv0;		/* Name of program */
int systype;		/* System type (from SYSTYPE or program argument) */
unsigned int qver;	/* Queue version number, from queue or best guess */
bool eswap;			/* Queue file with opposite endianness (default FALSE) */
char *xyzfile;		/* Name of XYZ file */
time_t xyztime;		/* Modification time of XYZ file */
char *fdir;			/* Name of folding directory */
char *title;		/* Title of window */

/* Font-specific variables */

int fscw;			/* Character width */
int fsch;			/* Character height */
int fsbh;			/* Button height */
int fsbs;			/* Extra button width, before and after label */

Display *dpy;
Window win;
GC gc;
GLXContext cx;
Window glwin;
XVisualInfo *vi;
XEvent event;
int xsiz, ysiz;
int xwsiz, ywsiz;
int ytop;
int bselect;
int lmbselect;
int lsbselect;
int model;
int debug;
unsigned long orange, gray30, gray80, green, yellow, aqua, plum;
float rotx, roty, rotz;
float dvx, dvy, dvz;
float ox, oy, oz;
float zoom;
float zdist;
float xspan, yspan, nearest;
int qiselect;
int bpselect;
int mnotifyf;
int mousex;
int mousey;
int dnlen;
int nh2o;
int nurea;

#if !NOPMAP
Pixmap pixmap;
GLXPixmap pmap;
#endif
#if !NOGLU
GLUquadricObj *qobj;
#endif

/* Saved values from the currently rendered display */

int cmodel;
float czoom;
int cstflg;
float csdctr;
float cdqctr;
float cbsbsiz;
int coflags;

struct btn menu_buttons[MB_N + 1];	/* Main menu button locations */
struct btn scrn_buttons[MAXB + 1];	/* Screen button locations */

char wbuf[200];
char errmsg[200];
char filname[300];
char xyzname[300];
char qname[300];
char qibuf[1000];
char ebuf[8];

void draw_scene(void);		/* Render the OpenGL bitmap image of the molecule */
void qd(void);				/* Set up queue information in QI screen */

/* Inquire about the state of a button */

int binquire(int b)
{
int n, f;
float d;

	f = coflags ^ oflags;
	switch (b)
	{
	case MB_SF:		/* SF */
	case HB_SF:		/* SF */
		n = MB_SF;
		goto md;
	case MB_BS:		/* BS */
	case HB_BS:		/* BS */
		n = MB_BS;
		goto md;
	case MB_WF:		/* WF */
	case HB_WF:		/* WF */
		n = MB_WF;
		goto md;
	case MB_AT:		/* AT */
	case HB_AT:		/* AT */
		n = MB_AT;
md:		if (model == n)
		{	if (cmodel != n)
				return (BS_P);
			return (BS_T);
		}
		if (cmodel == n)
			return (BS_N);
		return (BS_F);
	case HB_ST:		/* STEREO */
	case MB_ST:		/* STEREO */
		n = menu[MB_ST].flags;
		if ((n & BF_T) != 0)
		{	if	(	(cstflg != n)
				||	(csdctr != sldtab[0].val)
				||	((f & OF_RL) != 0)
				) return (BS_P);
			return (BS_T);
		}
		if (cstflg != n)
			return (BS_N);
		return (BS_F);
	case HB_ZI:		/* ZOOM IN */
	case MB_ZI:		/* ZOOM IN */
		if ((czoom / zoom) > 1.001)
			return (BS_P);
		return (BS_F);
	case HB_ZO:		/* ZOOM OUT */
	case MB_ZO:		/* ZOOM OUT */
		if ((czoom / zoom) < 0.999)
			return (BS_P);
		return (BS_F);
	case MB_RE:		/* REDRAW */
		if	(	(cmodel == model)
			&&	(((d = czoom / zoom) <= 1.001) && (d >= 0.999))
			&&	(	((cstflg & BF_T) == 0)
				||	(	(csdctr == sldtab[0].val)
					&&	((f & OF_RL) == 0)
					)
				)
			&&	(	(cmodel != MB_BS)
				||	(cbsbsiz == sldtab[2].val)
				)
			&&	(cdqctr == sldtab[1].val)
			&&	(cstflg == menu[MB_ST].flags)
			&&	(	(cmodel == MB_AT)
				||	(	((f & (OF_BO | OF_IH)) == 0)
					&&	(	(nh2o == 0)
						||	((f & OF_IW) == 0)
						)
					&&	(	(nurea == 0)
						||	((f & OF_IU) == 0)
						)
					)
				)
			&&	(	(cmodel == MB_SF)
				||	((f & OF_BI) == 0)
				)
			&&	(ywsiz == ytop + ysiz)
			&&	(xwsiz == xsiz)
			) return (BS_F);
		return (BS_N);
	case MB_HE:		/* HELP */
		n = menu[MB_HE].flags;
		goto mt;
	case MB_OP:		/* OPTIONS */
		n = menu[MB_OP].flags;
		goto mt;
	case MB_QI:		/* QI */
		n = menu[MB_QI].flags;
mt:		if ((n & BF_T) != 0)
			return (BS_T);
		return (BS_F);
	case OB_RI:		/* Redraw immediately when change selected */
		if ((oflags & OF_RR) == 0)
			return (BS_T);
		return (BS_F);
	case OB_RR:		/* Redraw only on REDRAW or NEW VIEW */
		n = OF_RR;
		goto tf;
	case OB_LR:		/* Stereo [L R] */
		if ((oflags & OF_RL) == 0)
		{	if	(	((cstflg & BF_T) != 0)
				&&	((coflags & OF_RL) != 0)
				) return (BS_P);
			return (BS_T);
		}
		if	(	((cstflg & BF_T) != 0)
			&&	((coflags & OF_RL) == 0)
			) return (BS_N);
		return (BS_F);
	case OB_RL:		/* Stereo [R L] */
		if ((oflags & OF_RL) != 0)
		{	if	(	((cstflg & BF_T) != 0)
				&&	((coflags & OF_RL) == 0)
				) return (BS_P);
			return (BS_T);
		}
		if	(	((cstflg & BF_T) != 0)
			&&	((coflags & OF_RL) != 0)
			) return (BS_N);
		return (BS_F);
	case OB_NC:		/* No change after rotation */
		if ((oflags & OF_DM) == 0)
			return (BS_T);
		return (BS_F);
	case OB_SF:		/* SF after rotation */
		n = MB_SF;
		goto dm;
	case OB_BS:		/* BS after rotation */
		n = MB_BS;
		goto dm;
	case OB_WF:		/* WF after rotation */
		n = MB_WF;
		goto dm;
	case OB_AT:		/* AT after rotation */
		n = MB_AT;
dm:		if (((oflags & OF_DM) != 0) && (rmodel == n))
			return (BS_T);
		return (BS_F);
	case OB_VF:		/* NEW VIEW can select SF model */
		n = OF_VF;
		goto tf;
	case OB_VB:		/* NEW VIEW can select BS model */
		n = OF_VB;
		goto tf;
	case OB_VW:		/* NEW VIEW can select WF model */
		n = OF_VW;
		goto tf;
	case OB_VA:		/* NEW VIEW can select AT model */
		n = OF_VA;
		goto tf;
	case OB_VS:		/* NEW VIEW can select Stereo */
		n = OF_VS;
		goto tf;
	case OB_VR:		/* NEW VIEW can change Rotation */
		n = OF_VR;
		goto tf;
	case OB_VZ:		/* NEW VIEW can reset Zoom */
		n = OF_VZ;
		goto tf;
	case OB_VO:		/* NEW VIEW can reset Origin */
		n = OF_VO;
		goto tf;
	case OB_BO:		/* Display backbone only */
		n = OF_BO;
		goto tf;
	case OB_IH:		/* Don't display hydrogen atoms */
		n = OF_IH;
		goto tf;
	case OB_IW:		/* Don't display water molecules */
		n = OF_IW;
		goto tf;
	case OB_IU:		/* Don't display urea molecules */
		n = OF_IU;
tf:		if ((oflags & n) != 0)
			return (BS_T);
		return (BS_F);
	case OB_BF:		/* Use bond information in XYZ file */
		if ((oflags & OF_BI) == 0)
		{	if	(	((coflags & OF_BI) != 0)
				&&	(cmodel != MB_SF)
				) return (BS_P);
			return (BS_T);
		}
		if	(	((coflags & OF_BI) == 0)
			&&	(cmodel != MB_SF)
			) return (BS_N);
		return (BS_F);
	case OB_BP:		/* Derive bonds from XYZ positions */
		if ((oflags & OF_BI) != 0)
		{	if	(	((coflags & OF_BI) == 0)
				&&	(cmodel != MB_SF)
				) return (BS_P);
			return (BS_T);
		}
		if	(	((coflags & OF_BI) != 0)
			&&	(cmodel != MB_SF)
			) return (BS_N);
		return (BS_F);

	case OB_RS:		/* Remember parameter settings */
		return (BS_F);
	}
	if ((b >= QB_QD) && (b < QB_QD + 20))
	{	if (b == qiselect)
			return (BS_T);
		return (BS_F);
	}
	return (BS_Q);		/* Unknown */
}

/* Clear a rectangular area of the screen */

void fillblack(int x, int y, int w, int h)
{
	XSetForeground(dpy, gc, 0);
	XFillRectangle(dpy, win, gc, x, y, w, h);
}

/* Draw a line on the screen */

void drawline(int x1, int y1, int x2, int y2)
{
	XDrawLine(dpy, win, gc, x1, y1, x2, y2);
}

/* Clear the screen when switching to a text screen */

void clrscreen(void)
{
	fillblack(0, ytop, xwsiz, ywsiz - ytop);
}

/* Calculate color bits */

unsigned long color(float r, float g, float b)
{
	return	(	((unsigned long) (r * (float) vi->red_mask) & vi->red_mask)
			|	((unsigned long) (g * (float) vi->green_mask) & vi->green_mask)
			|	((unsigned long) (b * (float) vi->blue_mask) & vi->blue_mask)
			);
}

/* Display menu buttons */

void dmenu(void)
{
int i, j, k;
unsigned long fg;
int y;
int row;
struct mbtn *pm;
struct btn *pb;

	k = (M_LW + 1) / 2;
	for (i = 0; menu[i].flags != 0 ; ++i)
	{	pm = &menu[i];
		pb = &menu_buttons[i];
		switch (pm->flags & BF_E)
		{
		case 1:			/* SF */
			j = MB_SF;
			goto st;
		case 2:			/* BS */
			j = MB_BS;
			goto st;
		case 3:			/* WF */
			j = MB_WF;
			goto st;
		case 4:			/* AT */
			j = MB_AT;
st:			pm->flags &= ~(BF_T | BF_A);
			if (model == j)
			{	pm->flags |= BF_T;
				if	(	(cmodel != j)
					&&	((oflags & OF_RR) == 0)
					&&	(((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) == 0)
					) pm->flags |= BF_A;
			}
		}

		y = k + fsbh * pm->row;
		if ((i == bselect) || (i == lmbselect))
		{	if ((i == bselect) && ((pm->flags & (BF_I | BF_T | BF_A)) != (BF_I | BF_T)))
				XSetForeground(dpy, gc, gray30);
			else
				XSetForeground(dpy, gc, 0);
			XFillRectangle(dpy, win, gc, pb->left, y, pb->right - pb->left, fsbh);
		}
		j = binquire(i);
		if (j == BS_T)		/* "Toggled" button state */
			fg = green;
		else if (j == BS_N)	/* "Pending off" button state */
			fg = aqua;
		else if (j == BS_P)	/* "Pending on" button state */
			fg = plum;
		else				/* "Normal" or unknown button state */
			fg = orange;
		XSetForeground(dpy, gc, fg);
		XDrawString(dpy, win, gc, pb->left + fsbs, y + (fsch * 2 + fsbh * 3 + 3) / 6, pm->text, pm->len);
	}
	lmbselect = bselect;
	row = -1;
	y = k;
	XSetForeground(dpy, gc, yellow);
	for (i = 0; ; ++i)
	{	pm = &menu[i];
		pb = &menu_buttons[i];
		if (row != pm->row)
		{	drawline(0, y, xwsiz, y);
			if (pm->flags == 0)
				break;
			row = pm->row;
			y += fsbh;
		}
		drawline(pb->right, y - fsbh, pb->right, y);
	}
	drawline(k, k, k, y);
	j = xwsiz - k;
	drawline(j, k, j, y);
}

/* Initialize menu button positions and set default screen width */

void imenu(void)
{
int i, j, k;
struct mbtn *pm;
struct btn *pb;

	ytop = -1;
	k = (M_LW + 1) / 2;
	j = k;
	for (i = 0; menu[i].flags != 0; ++i)
	{	pm = &menu[i];
		pb = &menu_buttons[i];
		pb->id = i;
		if ((i != 0) && (pm->row != (pm - 1)->row))
			j = k;
		pb->left = j;
		pm->len = strlen(pm->text);
		j += fscw * pm->len + 2 * fsbs - 1;
		pb->right = j;
		if (j + k > xsiz)
			xsiz = j + k;
		if (pm->row > ytop)
			ytop = pm->row;
		pb->top = k + fsbh * pm->row;
		pb->bottom = pb->top + fsbh;
	}
	ytop = (ytop + 1) * fsbh + M_LW;
	menu_buttons[i].id = -1;
}

/* Do special menu displays */

void dspecial(int n, int m, int *px, int *py)
{
int i, j, k;
float r, g, b;
float *pa;
char txt[20];
struct sldr *ps;

	switch (n)
	{
	case 'a':				/* Atom color key */
		for (i = 0; i <= 9; ++i)
		{	if (i < 7)
			{	sprintf(txt, "%c", ETYPES[i]);
				pa = aprops[i].color;
			}
			else if (i == 7)
			{	strcpy(txt, "    amino");
				pa = acprops[1].color;
			}
			else if (i == 8)
			{	strcpy(txt, "alpha");
				pa = acprops[0].color;
			}
			else
			{	strcpy(txt, "carboxyl");
				pa = acprops[2].color;
			}
			r = pa[0] * 1.25 + 0.1;
			g = pa[1] * 1.25 + 0.1;
			b = pa[2] * 1.25 + 0.1;
			if (r > 1.0) r = 1.0;
			if (g > 1.0) g = 1.0;
			if (b > 1.0) b = 1.0;
			XSetForeground(dpy, gc, color(r, g, b));
			j = strlen(txt);
			XDrawString(dpy, win, gc, *px + fscw / 2, *py + (fsch * 5 + 3) / 6, txt, j);
			*px += fscw * (j + 1);
		}
		break;

	case 'b':				/* Slider widget */
		ps = &sldtab[m - '1'];
		i = fscw / 2;		/* delta */
		if (bpselect == ps->id)		/* The slider is being moved */
		{	if (bselect != ps->id)
				bpselect = -1;
			else
			{	r = (float)(mousex - *px - fscw / 2 - i) / (float)(fscw * ps->len - 2 * i);
				if (r < 0.0) r = 0.0;
				if (r > 1.0) r = 1.0;
				ps->val = ps->k1 + ps->k2 * r;
				mnotifyf = TRUE;		/* We need to redisplay if the mouse moves */
			}
		}
		switch (ps->nds)	/* Numeric display style after slider */
		{
		default:
		case 0:				/* No number after slider */
			txt[0] = '\0';
			break;
		case 1:				/* Integer (-nn, 0, +nn) */
			j = (int) floor(ps->val + 0.5);
			sprintf(txt, "%+4d", j);
			break;
		case 2:				/* Percentage (nn%) */
			sprintf(txt, "%4u%%", (int) floor(ps->val * 100.0 + 0.5));
			break;
		}

		i = fscw / 2;		/* delta */
		XSetForeground(dpy, gc, orange);
		*px += fscw / 2;
		drawline(*px, *py + 1, *px, *py + fsch - 1);
		j = *px + fscw * ps->len;
		drawline(j, *py + 1, j, *py + fsch - 1);
		j = *py + fsch / 2;
		k = *px + i + (int)((ps->val - ps->k1) * (float)(fscw * ps->len - 2 * i) / ps->k2 + 0.5);
		drawline(k - i, j, *px, j);
		drawline(k + i, j, *px + fscw * ps->len, j);
		drawline(k - i, j, k, *py + 1);
		drawline(k - i, j, k, *py + fsch - 1);
		drawline(k + i, j, k, *py + 1);
		drawline(k + i, j, k, *py + fsch - 1);
		*px += fscw * ps->len + fscw / 2;

		if ((j = strlen(txt)) != 0)
		{	if (bpselect == ps->id)		/* The data might be different */
				fillblack(*px, *py - 1, fscw * j, fsch + 2);
			XSetForeground(dpy, gc, gray80);
			XDrawString(dpy, win, gc, *px - fscw / 2, *py + (fsch * 5 + 3) / 6, txt, j);
			*px += fscw * j;
		}
		break;
	}
}

/* Generic text screen with buttons display */

void dscreen(struct sbtn *ps)
{
int x, y;
int i, j, k;
unsigned long fg;
struct btn *pb;
char *p;

	pb = scrn_buttons;
	x = 0;
	y = ytop + fsch / 2;
	for ( ; (i = ps->flags) > 0; ++ps)
	{	if ((i & (BF_R | BF_S | BF_C)) != 0)
		{	x = 0;
			y += fsch;
		}
		if ((i & BF_S) != 0)
			y += fsch / 2;
		if ((i & BF_C) != 0)
			y += 2;
		k = BS_Q;
		if ((j = ps->id) >= 0)
		{	pb->id = j;
			pb->top = y - 1;
			pb->bottom = y + fsch + 1;
			pb->left = x;
			pb->right = x + fscw * (ps->len + 1);
			k = binquire(j);
			if ((j == bselect) || (j == lsbselect))
			{	if ((j == bselect) && (((i & BF_I) == 0) || (k == BS_F)))
					XSetForeground(dpy, gc, gray30);
				else
					XSetForeground(dpy, gc, 0);
				XFillRectangle(dpy, win, gc, x, y - 1, pb->right - pb->left, fsch + 2);
			}
			++pb;
		}
		p = ps->text;
		i = ps->len;
		if ((j = p[0] & 0xFF) == 0377)
		{	dspecial(p[1] & 0xFF, p[2] & 0xFF, &x, &y);
			continue;
		}
		if ((ps->flags & BF_Z) != 0)
			fillblack(x, y - 1, fscw * (i + 1), fsch + 2);
		switch (k)
		{
		case BS_F:	fg = orange;	break;		/* "Normal" button state */
		case BS_P:	fg = plum;		break;		/* "Pending on" button state */
		case BS_N:	fg = aqua;		break;		/* "Pending off" button state */
		case BS_T:	fg = green;		break;		/* "Toggled" button state */
		default:	fg = gray80;	break;		/* No button indication */
		}
		XSetForeground(dpy, gc, fg);
		XDrawString(dpy, win, gc, x + (fscw + 1) / 2, y + (fsch * 5 + 3) / 6, p, i);
		x += fscw * (i + 1);
	}
	lsbselect = bselect;
	pb->id = -1;
}

/* Initialize text screen string lengths */

void iscreen(struct sbtn *ps)
{
	for ( ; ps->flags > 0; ++ps)
	{	ps->len = strlen(ps->text);
		if (((ps->text[0] & 0xFF) == 0377) && (ps->text[1] == 'b'))
			sldtab[ps->text[2] - '1'].len = ps->len;
	}
}

/* Check for mouse over a button */

int ckbutton(struct btn *pb)
{
	while	(	(pb->id != -1)
			&&	(	(mousex <= pb->left)
				||	(mousex >= pb->right)
				||	(mousey <= pb->top)
				||	(mousey >= pb->bottom)
				)
			) ++pb;
	return (pb->id);
}

/* Convert string to upper case */

void cvup(char *q, char *p, int n)
{
int i;

	while ((--n > 0) && ((i = *p++) != '\0'))
	{	if ((i >= 'a') && (i <= 'z'))
			i -= 'a' - 'A';
		*q++ = i;
	}
	*q = 0;
}

/* Interpret model type string */

int ckmodel(char *p)
{
char buf[20];

	cvup(buf, p, sizeof(buf));
	if (strcmp(buf, "SF") == 0) return (MB_SF);
	if (strcmp(buf, "BS") == 0) return (MB_BS);
	if (strcmp(buf, "WF") == 0) return (MB_WF);
	if (strcmp(buf, "AT") == 0) return (MB_AT);
	if (strcmp(buf, "SAME") == 0) return (-2);
	return (-1);
}

/* Set up window title */

void dotitle(void)
{
int i, j;
char *p, *q;
time_t t;
char wnam[300];

	if (pname[0] == '\0')
		strcpy(pname, "Protein");
	q = wnam;
	q[sizeof(wnam) - 1] = '\0';
	for (p = title; (*p != '\0') && (q < wnam + sizeof(wnam) - 1); )
	{	if	(	((i = *p++) != '%')
			||	((i = *p++) == '%')
			) *q++ = i;
		else
		{	j = wnam + sizeof(wnam) - 1 - q;
			if (i == 't')
			{	time(&t);
				strncpy(q, ctime(&t), j);
				q += strlen(q) - 1;
			}
			else if (i == 'h')
			{	if (gethostname(q, j) == 0)
					goto qq;
				*q++ = '?';
			}
			else if (i == 'p')
			{	strncpy(q, pname, j);
qq:				q += strlen(q);
			}
			else
				--p;
		}
	}
	*q = 0;
	XStoreName(dpy, win, wnam);
}

int sq(int x)
{	return (x * x);
}

void rot2(float angle, float *x, float *y)
{
float a, b, s, c;

	if (debug >= 5)
		printf("rot2(%g, %g, %g)", angle, *x, *y);
	a = *x;
	b = *y;
	s = sin(angle);
	c = cos(angle);
	*x = a * c - b * s;
	*y = b * c + a * s;
	if (debug >= 5)
		printf(" -> (%g, %g)\n", *x, *y);
}

#if NOGLU

/* Display cylinder (if not using GLU functions) */

void dCylinder(float r, float h, int n)
{
float x, y;
float a;
int theta;

	n += n / 2;
	if (n > 100) n = 100;
	n += (100 - n) % 4;
	glBegin(GL_QUAD_STRIP);
	for (theta = 0; theta <= n; ++theta)
	{	a = (float) theta * M_PI*2.0 / (float) n;
		x = cos(a) * r;
		y = sin(a) * r;
		glNormal3f(x, y, 0.0);
		glVertex3f(x, y, h);
		glVertex3f(x, y, 0.0);
	}
	glEnd();
}

/* Display sphere (if not using GLU functions) */

void dSphere(float r, int n)
{
float x, y, z;
float zs;
float cs;
float c;
int phi, theta;
float st[101], ct[101];

	n += n / 2;
	if (n > 100) n = 100;
	n += (100 - n) % 4;
	for (theta = 0; theta <= n; ++theta)
	{	x = (float) theta * M_PI*2.0 / (float) n;
		st[theta] = sin(x);
		ct[theta] = cos(x);
	}
	c = 0.0;
	z = r;
	for (phi = 1; phi <= n / 4; ++phi)
	{	cs = c;
		zs = z;
		c = st[phi] * r;
		z = ct[phi] * r;
		glBegin((phi == 1) ? GL_TRIANGLE_FAN : GL_QUAD_STRIP);
		for (theta = 0; theta <= n; ++theta)
		{	if ((phi != 1) || (theta == 0))
			{	x = ct[theta] * cs;
				y = st[theta] * cs;
				glNormal3f(x, y, zs);
				glVertex3f(x, y, zs);
			}
			x = ct[theta] * c;
			y = st[theta] * c;
			glNormal3f(x, y, z);
			glVertex3f(x, y, z);
		}
		glEnd();
		glBegin((phi == 1) ? GL_TRIANGLE_FAN : GL_QUAD_STRIP);
		for (theta = 0; theta <= n; ++theta)
		{	if ((phi != 1) || (theta == 0))
			{	x = ct[theta] * cs;
				y = -st[theta] * cs;
				glNormal3f(x, y, -zs);
				glVertex3f(x, y, -zs);
			}
			x = ct[theta] * c;
			y = -st[theta] * c;
			glNormal3f(x, y, -z);
			glVertex3f(x, y, -z);
		}
		glEnd();
	}
}

#endif

static int attributeListSgl[] =
 { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DEPTH_SIZE, 1, None };

static int attributeListDbl[] =
 { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DEPTH_SIZE, 1, None };

#if NOPMAP

static Bool WaitForExpose(Display *d, XEvent *e, char *arg)
{
	return (e->type == Expose);		/* The window number sometimes doesn't match ?? */
}

#endif

int main(int argc, char **argv)
{
int i, j;
bool bf;
int x1, x1r, x2, y1, y1r, y2;
char *p;
float by1, by2, by3, bz1, bz2, bz3, c1, c2, c3, dd, ee;
float dx, dy, dz, d;
int *pd;
float *pf;
char *fontname;
char *c_size, *c_model, *c_smode, *c_rmodel, *c_systype;
char zc;
int zx, zy;
char buf[20];
XSetWindowAttributes swa;
XFontStruct *font;
bool swap_flag = FALSE;

	argv0 = argv[0];
	systype = SYSTYPE;		/* Default is native queue type */
	qver = DEFQVER;
	bselect = -1;
	mnotifyf = FALSE;
	zoom = IZOOM;
	model = MB_SF;
	sldtab[0].val = 0.0;
	sldtab[1].val = 0.0;
	sldtab[2].val = BSBSIZ;
	qiselect = -1;
	bpselect = -1;
	oflags = OF_VF | OF_VB | OF_VR | OF_VZ | OF_VO | OF_BI;
	coflags = oflags;
	atom = NULL;

	fontname = FONT;
	xyzfile = XYZFILE;
	xyztime = 0;
	title = "%p   %t";
	fdir = NULL;
	debug = 0;
	c_size = NULL;
	c_model = NULL;
	c_smode = NULL;
	c_rmodel = NULL;
	c_systype = NULL;

	rotx = 0.0;		/* Current rotation */
	roty = 0.0;
	rotz = 0.0;
	dvx = 0.0;		/* Current direction of view */
	dvy = 0.0;
	dvz = 1.0;
	ox = 0.0;		/* Current origin, for zoom and rotation */
	oy = 0.0;
	oz = 0.0;

	/* Process the command-line flags */

	while (--argc > 0)
	{	p = *++argv;
		if (*p++ != '-')
			goto us;
		if (*p == '-')
			++p;
		if (strcmp(p, "size") == 0)
		{	c_size = *++argv;
			goto ca;
		}
		else if (strcmp(p, "font") == 0)
		{	fontname = *++argv;
			goto ca;
		}
		else if (strcmp(p, "xyzfile") == 0)
		{	xyzfile = *++argv;
			oflags |= OF_XF;
			goto ca;
		}
		else if (strcmp(p, "dir") == 0)
		{	fdir = *++argv;
			goto ca;
		}
		else if (strcmp(p, "zoom") == 0)
		{	pf = &zoom;
			goto gf;
		}
		else if (strcmp(p, "model") == 0)
		{	c_model = *++argv;
			goto ca;
		}
		else if (strcmp(p, "smode") == 0)
		{	c_smode = *++argv;
			goto ca;
		}
		else if (strcmp(p, "sdep") == 0)
		{	pf = &sldtab[0].val;
			goto gf;
		}
		else if (strcmp(p, "dqual") == 0)
		{	pf = &sldtab[1].val;
			goto gf;
		}
		else if (strcmp(p, "systype") == 0)
		{	c_systype = *++argv;
			goto ca;
		}
		else if (strcmp(p, "bsbsiz") == 0)
		{	pf = &sldtab[2].val;
gf:			if	(	(--argc <= 0)
				||	(sscanf(*++argv,"%f%c", pf, &zc) != 1)
				) goto ma;
		}
		else if (strcmp(p, "rmodel") == 0)
		{	c_rmodel = *++argv;
ca:			if (--argc <= 0)
			{	fprintf(stderr, "\n\
%s: Missing argument after \"-%s\"", argv0, p);
				goto us;
			}
		}
		else if (strcmp(p, "debug") == 0)
		{	pd = &debug;
			if	(	(--argc <= 0)
				||	(sscanf(*++argv,"%d%c", pd, &zc) != 1)
				)
ma:			{	fprintf(stderr, "\n\
%s: Bad or missing numeric argument after \"-%s\"", argv0, p);
				goto us;
			}
		}
		else if (strcmp(p, "stereo") == 0)
			menu[MB_ST].flags |= BF_T;
		else if (strcmp(p, "redraw") == 0)
			oflags |= OF_RR;
		else if (strcmp(p, "title") == 0)
		{	title = *++argv;
			goto ca;
		}
		else if (strcmp(p, "version") == 0)
		{	fprintf(stderr, "%s\n", version);
			exit(0);
		}
		else		/* Including "-help" */
		{
us:			fprintf(stderr, "\n\
Usage: %s [<flags> with arguments, as follows]\n\n\
 -size <width>x<height>            [418x418] with \"10x20\" font\n\
 -font <font name>                 [10x20]\n\
 -xyzfile <XYZ file name>          [work/current.xyz]\n\
 -dir <folding directory>          [.]\n\
 -zoom <initial zoom>              0.5 to 10.0 [3.0]\n\
 -model <initial model>            SF, BS, WF, or AT [SF]\n\
 -smode <initial stereo mode>      LR or RL [LR]\n\
 -sdep <initial stereo depth>      -10 to 10 [0]\n\
 -dqual <initial display quality>  -10 to 10 [0]\n", argv0);
			fprintf(stderr, "\
 -systype <queue file type>        LINUX, WINDOWS, or MAC\n\
 -bsbsiz <initial BS ball size>    0.0 to 1.0 [0.2]\n\
 -rmodel <model after rotation>    same, SF, BS, WF, or AT [same]\n\
 -title <window title string>      [%%p   %%t]\n\
    string can use %%p - protein name\n\
                   %%h - host name\n\
                   %%t - current time\n\
 -debug <level>                    0 to 9 [0]\n\
 -stereo      Initial drawing in stereo mode\n\
 -redraw      Redraw only on REDRAW, NEW VIEW, or rotation\n");
			fprintf(stderr, "\
 -version     Print out version number and stop\n\
 -help        This information\n\n");
			exit(1);
		}
	}

	/* Process and do sanity checks on the command-line data */

	if ((i = debug) > 9)			/* Set up debug level first (!) */
		i = 9;
	if (i < 0)
		i = 0;
	if (i != debug)
		printf("Debug level %d out of range, setting to %d\n", debug, i);
	debug = i;

	xsiz = 0;
	ysiz = 0;
	if (c_size != NULL)
	{	if	(	(sscanf(c_size, "%dx%d%c", &zx, &zy, &zc) == 2)
			&&	(zx >= 30) && (zx <= 1600)
			&&	(zy >= 30) && (zy <= 1280)
			)
		{	xsiz = zx;
			ysiz = zy;
		}
		else
			printf("Invalid window size \"%s\", using default\n", c_size);
	}

	/* Load the font and derive some important properties */

	if ((dpy = XOpenDisplay(NULL)) == NULL)
	{	fprintf(stderr, "Can't open display\n");
		exit(1);
	}

	if ((font = XLoadQueryFont(dpy, fontname)) == NULL)
	{	fprintf(stderr, "Can't load font \"%s\"\n", fontname);
		exit(1);
	}

	fscw = font->max_bounds.rbearing - font->min_bounds.lbearing;	/* Character width */
	fsch = font->max_bounds.ascent + font->max_bounds.descent;		/* Character height */
	fsbh = (6 * fsch + 2) / 5 + 4 + M_LW;							/* Menu button height */
	fsbs = (9 * fscw + 5) / 10;										/* Extra button width */

	if (debug >= 1)
		printf("Font info: cw %d, ch %d, bh %d, bs %d\n", fscw, fsch, fsbh, fsbs);

	imenu();				/* Initialize menu button table, xsiz, and ytop */
	if (ysiz == 0)
		ysiz = xsiz;

	i = 0;
	if (fdir != NULL)
	{	strncpy(filname, fdir, sizeof(filname) - 20);
		filname[sizeof(filname) - 20] = '\0';
		i = strlen(filname);
		if (filname[i - 1] != '/')
		{	filname[i] = '/';
			filname[++i] = '\0';
		}
		strcpy(xyzname, filname);
		strcpy(qname, filname);
	}
	dnlen = i;
	strncpy(&qname[i], QFILE, sizeof(qname) - 1 - i);
	qname[sizeof(qname) - 1] = '\0';
	if (xyzfile[0] == '/')
		i = 0;
	strncpy(&xyzname[i], xyzfile, sizeof(xyzname) - 1 - i);
	xyzname[sizeof(xyzname) - 1] = '\0';

	if ((d = zoom) > 10.0)
		d = 10.0;
	if (d < 0.5)
		d = 0.5;
	if (d != zoom)
		printf("Initial zoom out of range, setting to %g\n", d);
	zoom = d;

	if (c_model != NULL)
	{	if ((i = ckmodel(c_model)) >= 0)
			model = i;
		else
			printf("Ignoring unknown model type \"%s\"\n", c_model);
	}

	if (c_smode != NULL)
	{	cvup(buf, c_smode, sizeof(buf));
		if (strcmp(buf, "RL") == 0)
			oflags |= OF_RL;
		else if (strcmp(buf, "LR") == 0)
			oflags &= ~OF_RL;
		else
			printf("Ignoring unknown stereo mode \"%s\"\n", c_smode);
	}

	if (c_systype != NULL)
	{	oflags |= OF_QT;
		cvup(buf, c_systype, sizeof(buf));
		if (strcmp(buf, "LINUX") == 0)
			systype = 0;
		else if (strcmp(buf, "WINDOWS") == 0)
			systype = 1;
		else if (strcmp(buf, "MAC") == 0)
			systype = 2;
		else
		{	printf("Ignoring unknown queue file type \"%s\"\n", c_systype);
			oflags &= ~OF_QT;
		}
	}

	if ((d = sldtab[0].val) > 10.0)
		d = 10.0;
	if (d < -10.0)
		d = -10.0;
	if (d != sldtab[0].val)
		printf("Initial stereo depth out of range, setting to %g\n", d);
	sldtab[0].val = d;

	if ((d = sldtab[1].val) > 10.0)
		d = 10.0;
	if (d < -10.0)
		d = -10.0;
	if (d != sldtab[1].val)
		printf("Initial display quality out of range, setting to %g\n", d);
	sldtab[1].val = d;

	if ((d = sldtab[2].val) > 1.0)
		d = 1.0;
	if (d < 0.0)
		d = 0.0;
	if (d != sldtab[2].val)
		printf("Initial BS model ball size out of range, setting to %g\n", d);
	sldtab[2].val = d;

	if (c_rmodel != NULL)
	{	if ((i = ckmodel(c_rmodel)) >= 0)
		{	rmodel = i;
			oflags |= OF_DM;
		}
		else if (i == -2)
			oflags &= ~OF_DM;
		else
			printf("Ignoring unknown model type (after rotation) \"%s\"\n", c_rmodel);
	}

	/* Set up a few things dependent on the size of the screen */

	xwsiz = xsiz;
	ywsiz = ytop + ysiz;
	iscreen(helpscreen);	/* Initialize help screen string lengths */
	iscreen(optsscreen);	/* Initialize options screen string lengths */

	/* Create the necessary X and OpenGL things */

	if ((vi = glXChooseVisual(dpy, DefaultScreen(dpy), attributeListSgl)) == NULL)
	{	vi = glXChooseVisual(dpy, DefaultScreen(dpy), attributeListDbl);
		swap_flag = TRUE;
	}
	if (vi == NULL)
	{	fprintf(stderr, "Can't choose visual\n");
		exit(1);
	}

	swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone);
	swa.border_pixel = 0;
	swa.event_mask = StructureNotifyMask | ExposureMask | PointerMotionMask
			| ButtonPressMask | ButtonReleaseMask | LeaveWindowMask | ResizeRedirectMask;
	win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, xwsiz, ywsiz,
			0, vi->depth, InputOutput, vi->visual,
			CWBorderPixel|CWColormap|CWEventMask, &swa);
	XMapWindow(dpy, win);
	do XNextEvent(dpy, &event);
	while ((event.type != MapNotify) || (event.xmap.window != win));

#if NOPMAP
	cx = glXCreateContext(dpy, vi, 0, GL_TRUE);		/* Direct rendering if supported */
#else
	cx = glXCreateContext(dpy, vi, 0, GL_FALSE);	/* No direct rendering with pixmap */
#endif
	gc = XCreateGC(dpy, win, 0, NULL);
	XSetLineAttributes(dpy, gc, M_LW, LineSolid, CapButt, JoinRound);
	XSetFont(dpy, gc, font->fid);
#if !NOGLU
	qobj = gluNewQuadric();
	gluQuadricDrawStyle(qobj, GLU_FILL);
#endif

	pname[0] = '\0';
	dotitle();

	fillblack(0, 0, xwsiz, ywsiz);
	if (swap_flag)
	{	glXSwapBuffers(dpy, win);	/* Not necessary here... */
		if (debug >= 1)
			printf("Using double buffered visual\n");
	}
	XSync(dpy, TRUE);		/* Get rid of queued expose events */

	/* Initialize color values */

	orange = color(1.0, 0.5, 0.0);
	gray30 = color(0.3, 0.3, 0.3);
	gray80 = color(0.8, 0.8, 0.8);
	green = color(0.0, 1.0, 0.0);
	yellow = color(1.0, 1.0, 0.0);
	aqua = color(0.0, 0.8, 0.8);
	plum = color(1.0, 0.5, 0.8);

#if NOPMAP
	glwin = win;			/* Render directly into the window */
#else
	pixmap = 0;				/* No pixmap yet */
#endif
	--ysiz;					/* Make it assign one */

	/* Now we actually get to the drawing... */

	draw_scene();
#if !NOPMAP
	XCopyArea(dpy, pixmap, win, gc, 0, 0, xsiz, ysiz, 0, ytop);
#endif
	dmenu();
	bf = FALSE;
	x1 = 0;
	y1 = -1;
	x2 = 0;
	x1r = 0;
	y1r = -1;
	for (;;)
	{	XNextEvent(dpy, &event);
		switch (event.type)
		{
		case Expose:
			i = event.xexpose.height;
			if ((j = event.xexpose.y - ytop) < M_LW)
			{	fillblack(event.xexpose.x, event.xexpose.y, event.xexpose.width, i);
				bf = TRUE;
			}
			if (j < 0)
			{	i += j;
				j = 0;
			}
			if (i > 0)
			{	if (((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) == 0)
#if NOPMAP
				{	XSync(dpy, TRUE);
					draw_scene();
					bf = TRUE;
					goto ds;
				}
#else
					XCopyArea(event.xexpose.display, pixmap, event.xexpose.window, gc,
							event.xexpose.x, j, event.xexpose.width, i,
							event.xexpose.x, j + ytop);
#endif
				else if (j != 0)
					fillblack(event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
			}
			if (event.xexpose.count == 0)
				goto ds;
			break;

		case MotionNotify:
			mousex = event.xmotion.x;
			mousey = event.xmotion.y;
cb:			if (mousey < ytop)
				i = ckbutton(menu_buttons);
			else if (((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) != 0)
				i = ckbutton(scrn_buttons);
			else
ns:				i = -1;
			if (i != bselect)
			{	bselect = i;
				goto dm;
			}
			if (mnotifyf)
			{	mnotifyf = FALSE;
				goto dm;
			}
			break;

		case ButtonPress:
			if (event.xbutton.button == Button3)
			{	x1r = event.xbutton.x;
				y1r = event.xbutton.y - ytop;
				continue;
			}
			y1r = -1;
			if (event.xbutton.button != Button1)
				continue;
			bpselect = bselect;
			if ((bselect == OB_SD) || (bselect == OB_DQ) || (bselect == OB_SS))
				goto dm;
			if (((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) != 0)
				continue;
			x1 = event.xbutton.x;
			y1 = event.xbutton.y - ytop;
			break;

		case ButtonRelease:
			if	(	(event.xbutton.button == Button3)
				&&	(y1r >= 0)
				) goto rb;
			if (event.xbutton.button != Button1)
				continue;
			x2 = -9999;
			bpselect = -1;
			if ((bselect >= QB_QD) && (bselect < QB_QD + 10))
			{	qiselect = bselect;
				goto qd;
			}
			switch (bselect)
			{
			case MB_HE:			/* HELP */
				clrscreen();
				if ((menu[MB_HE].flags & BF_T) != 0)
					goto hf;
				menu[MB_HE].flags |= BF_T;
				menu[MB_OP].flags &= ~BF_T;
				menu[MB_QI].flags &= ~BF_T;
				goto dm;
			case MB_OP:			/* OPTIONS */
				clrscreen();
				if ((menu[MB_OP].flags & BF_T) != 0)
					goto hf;
				menu[MB_OP].flags |= BF_T;
				menu[MB_HE].flags &= ~BF_T;
				menu[MB_QI].flags &= ~BF_T;
				goto dm;
			case MB_QI:			/* QI */
				clrscreen();
				if ((menu[MB_QI].flags & BF_T) != 0)
					goto hf;
				menu[MB_QI].flags |= BF_T;
				menu[MB_HE].flags &= ~BF_T;
				menu[MB_OP].flags &= ~BF_T;
				qiselect = -1;		/* Go back to the current unit */
qd:				qd();			/* Set up QI screen */
				mnotifyf = TRUE;
				goto cb;			/* Recheck button selection and display */
			case MB_SF:			/* SF */
			case HB_SF:			/* SF */
				i = MB_SF;
				goto dn;
			case MB_BS:			/* BS */
			case HB_BS:			/* BS */
				i = MB_BS;
				goto dn;
			case MB_WF:			/* WF */
			case HB_WF:			/* WF */
				i = MB_WF;
				goto dn;
			case MB_AT:			/* AT */
			case HB_AT:			/* AT */
				i = MB_AT;
dn:				j = model;
				model = i;
				if (i != cmodel)
					goto dr;
				if (i != j)
					goto dm;
				break;
			case MB_ZI:			/* ZOOM IN */
			case HB_ZI:			/* ZOOM IN */
				zoom /= ZOOMF;
				goto dr;
			case MB_ZO:			/* ZOOM OUT */
			case HB_ZO:			/* ZOOM OUT */
				zoom *= ZOOMF;
				goto dr;
			case MB_ST:			/* STEREO */
			case HB_ST:			/* STEREO */
				menu[MB_ST].flags ^= BF_T;
dr:				if	(	((oflags & OF_RR) != 0)
					||	(((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) != 0)
					) goto dm;
				goto rd;
			case MB_NV:			/* NEW VIEW */
				if ((oflags & (OF_VF | OF_VB | OF_VW | OF_VA)) != 0)	/* Change model */
					model = -1;
				while (model == -1)					/* Select new (random) model */
				{	if (((i = rand() % 4) == 0) && ((oflags & OF_VF) != 0))
						model = MB_SF;
					else if ((i == 1) && ((oflags & OF_VB) != 0))
						model = MB_BS;
					else if ((i == 2) && ((oflags & OF_VW) != 0))
						model = MB_WF;
					else if ((i == 3) && ((oflags & OF_VA) != 0))
						model = MB_AT;
				}
				if	(	((oflags & OF_VS) != 0)		/* Random selection of stereo */
					&&	((rand() & 2) != 0)
					) menu[MB_ST].flags ^= BF_T;
				if ((oflags & OF_VR) != 0)			/* Change rotation */
				{	rotx = (float) (rand() % 360);
					roty = (float) (rand() % 360);
					rotz = (float) (rand() % 360);
				}
				if ((oflags & OF_VZ) != 0)			/* Reset zoom */
					zoom = IZOOM;
				if ((oflags & OF_VO) != 0)			/* Reset origin */
				{	ox = 0.0;
					oy = 0.0;
					oz = 0.0;
				}
			case MB_RE:			/* REDRAW */
rd:
#if !NOPMAP
				draw_scene();
#endif
hf:				menu[MB_HE].flags &= ~BF_T;
				menu[MB_OP].flags &= ~BF_T;
				menu[MB_QI].flags &= ~BF_T;
#if NOPMAP
				draw_scene();
#else
				XCopyArea(dpy, pixmap, win, gc, 0, 0, xsiz, ysiz, 0, ytop);
#endif
dm:				bf = TRUE;
ds:				if ((menu[MB_HE].flags & BF_T) != 0)
					dscreen(helpscreen);	/* Display help screen */
				if ((menu[MB_OP].flags & BF_T) != 0)
					dscreen(optsscreen);	/* Display options screen */
				if ((menu[MB_QI].flags & BF_T) != 0)
					dscreen(qiscreen);		/* Display queue information screen */
				if (bf)
				{	dmenu();
					bf = FALSE;
				}
				break;
			case MB_QU:			/* QUIT */
				exit(0);
			case OB_RI:			/* Redraw immediately when change selected */
				oflags &= ~OF_RR;
				goto ds;
			case OB_RR:			/* Redraw only on REDRAW or NEW VIEW */
				oflags |= OF_RR;
				goto ds;
			case OB_LR:			/* Stereo [L R] */
				oflags &= ~OF_RL;
				goto dm;
			case OB_RL:			/* Stereo [R L] */
				oflags |= OF_RL;
				goto dm;
			case OB_NC:			/* No change after rotation */
				oflags &= ~OF_DM;
				goto ds;
			case OB_SF:			/* SF after rotation */
				rmodel = MB_SF;
				goto sm;
			case OB_BS:			/* BS after rotation */
				rmodel = MB_BS;
				goto sm;
			case OB_WF:			/* WF after rotation */
				rmodel = MB_WF;
				goto sm;
			case OB_AT:			/* AT after rotation */
				rmodel = MB_AT;
sm:				oflags |= OF_DM;
				goto ds;
			case OB_VF:			/* NEW VIEW can select SF model */
				i = OF_VF;
				goto so;
			case OB_VB:			/* NEW VIEW can select BS model */
				i = OF_VB;
				goto so;
			case OB_VW:			/* NEW VIEW can select WF model */
				i = OF_VW;
				goto so;
			case OB_VA:			/* NEW VIEW can select AT model */
				i = OF_VA;
				goto so;
			case OB_VS:			/* NEW VIEW can select Stereo */
				i = OF_VS;
				goto so;
			case OB_VR:			/* NEW VIEW can change Rotation */
				i = OF_VR;
				goto so;
			case OB_VZ:			/* NEW VIEW can reset Zoom */
				i = OF_VZ;
				goto so;
			case OB_VO:			/* NEW VIEW can reset Origin */
				i = OF_VO;
so:				oflags ^= i;
				goto ds;
			case OB_BO:			/* Display backbone only */
				i = OF_BO;
				goto si;
			case OB_IH:			/* Don't display hydrogen atoms */
				i = OF_IH;
				goto si;
			case OB_IW:			/* Don't display water molecules */
				i = OF_IW;
				goto si;
			case OB_IU:			/* Don't display urea molecules */
				i = OF_IU;
si:				oflags ^= i;
				goto dm;
			case OB_BF:			/* Use bond information in XYZ file */
				oflags &= ~OF_BI;
				goto dm;
			case OB_BP:			/* Derive bonds from XYZ positions */
				oflags |= OF_BI;
				goto dm;
			case OB_RS:			/* Remember settings */
/***/
				break;
			default:			/* Mouse release not on button */
				break;
			}
			if (x2 != -9999)
				continue;
rb:			x2 = event.xbutton.x;
			y2 = event.xbutton.y - ytop;
			if (((menu[MB_HE].flags | menu[MB_OP].flags | menu[MB_QI].flags) & BF_T) != 0)
				continue;
			if	(	(x2 > xsiz)
				||	(y2 > ysiz)
				)
			{	y1 = -1;
				y1r = -1;
				continue;
			}

			if (event.xbutton.button == Button3)	/* Right mouse button */
			{	if	(	(y1r < 0)
					||	(y2 < 0)
					)
				{	y1r = -1;
					continue;
				}

	/* Mouse right button dragged over molecule, do translation */

				bz1 = xspan * (float) (x2 - x1r) / (float) xsiz;
				bz2 = yspan * (float) (y1r - y2) / (float) ysiz;
				bz3 = 0.0;
				if (bz1 * bz1 + bz2 * bz2 == 0.0)
					continue;		/* No motion, no need to redisplay */
				if (debug >= 4)
					printf("Right button released: xspan %g, yspan %g, dx %g, dy %g, o (%g, %g, %g)\n",
							xspan, yspan, bz1, bz2, ox, oy, oz);

				rot2(-rotx * M_PI/180.0, &bz2, &bz3);		/* Unrotate translation to match molecule coordinates */
				rot2(-roty * M_PI/180.0, &bz3, &bz1);
				rot2(-rotz * M_PI/180.0, &bz1, &bz2);

				ox -= bz1;			/* Adjust origin in molecule coordinates */
				oy -= bz2;
				oz -= bz3;
				goto rd;
			}

	/* Mouse left button dragged over molecule, do rotation */

			if	(	(y1 < 0)
				||	(y2 < 0)
				)
			{	y1 = -1;
				continue;
			}

			if (debug >= 4)
				printf("Left button released: (%d,%d), (%d,%d)\n", x1, y1, x2, y2);
			d = sqrt((float) (sq(x1 + x2 - xsiz) + sq(y1 + y2 - ysiz)) / (float) (xsiz * ysiz));
			dz = M_PI * d * ((x2 * y1 - x1 * y2) * 2 - (x2 - x1) * ysiz + (y2 - y1) * xsiz) / (float) (sq(xsiz) + sq(ysiz));
			if ((d = M_PI/2 * (1 - d)) < 0) d = 0.0;
			dx = d * (float) (y2 - y1) / (float) ysiz;
			dy = d * (float) (x2 - x1) / (float) xsiz;
			if ((d = sqrt(dx * dx + dy * dy + dz * dz)) == 0.0)
				continue;		/* No motion, no need to recalculate */
			dx /= d;
			dy /= d;
			dz /= d;
			if (debug >= 4)
				printf("Current (x, y, z) = (%g, %g, %g)\n", rotx, roty, rotz);

	/* Define Y and Z basis vectors, rotate to match molecule on screen */

			by1 = 0.0;
			by2 = 1.0;
			by3 = 0.0;
			rot2(rotz * M_PI/180.0, &by1, &by2);
			rot2(roty * M_PI/180.0, &by3, &by1);
			rot2(rotx * M_PI/180.0, &by2, &by3);

			bz1 = 0.0;
			bz2 = 0.0;
			bz3 = 1.0;
			rot2(roty * M_PI/180.0, &bz3, &bz1);
			rot2(rotx * M_PI/180.0, &bz2, &bz3);

	/* Rotate basis vectors according to the mouse motion */

			if (debug >= 4)
			{	printf("Protein Y = (%g, %g, %g), Z = (%g, %g, %g)\n", by1, by2, by3, bz1, bz2, bz3);
				printf("Mouse rotation d around (dx, dy, dz) = %g around (%g, %g, %g)\n", d, dx, dy, dz);
			}
			dd = atan2(dz, dy);
			c1 = dx;
			c2 = dy;
			c3 = dz;
			rot2(-dd, &c2, &c3);
			ee = atan2(c2, c1);
			if (debug >= 4)
			{	printf("dd = %g, ee = %g\n", dd, ee);
				rot2(-ee, &c1, &c2);
				printf("Rotated c (dx, dy, dz) = (%g, %g, %g)\n", c1, c2, c3);
			}

			rot2(-dd, &by2, &by3);
			rot2(-dd, &bz2, &bz3);
			rot2(-ee, &by1, &by2);
			rot2(-ee, &bz1, &bz2);
			rot2(d, &by2, &by3);
			rot2(d, &bz2, &bz3);
			rot2(ee, &by1, &by2);
			rot2(ee, &bz1, &bz2);
			rot2(dd, &by2, &by3);
			rot2(dd, &bz2, &bz3);
			if (debug >= 4)
				printf("-> target Y = (%g, %g, %g), Z = (%g, %g, %g)\n", by1, by2, by3, bz1, bz2, bz3);

			dvx = 0.0;				/* Also calculate direction of view */
			dvy = 0.0;
			dvz = 1.0;

	/* B is now in the target rotation, read it out into the protein view */

			dd = -atan2(bz2, bz3);
			if (debug >= 4)
				printf("Rot x = -atan2(%g, %g) = %g\n", bz2, bz3, dd);
			rot2(-dd, &by2, &by3);
			rot2(-dd, &bz2, &bz3);
			rot2(-dd, &dvy, &dvz);
			if (debug >= 4)
				printf("Y = (%g, %g, %g), Z = (%g, %g, %g)\n", by1, by2, by3, bz1, bz2, bz3);
			rotx = 180.0/M_PI * dd;

			dd = atan2(bz1, bz3);
			rot2(-dd, &by3, &by1);
			rot2(-dd, &dvz, &dvx);
			if (debug >= 4)
			{	printf("Rot y = atan2(%g, %g) = %g\n", bz1, bz3, dd);
				rot2(-dd, &bz3, &bz1);
				printf("Y = (%g, %g, %g), Z = (%g, %g, %g)\n", by1, by2, by3, bz1, bz2, bz3);
			}
			roty = 180.0/M_PI * dd;

			dd = -atan2(by1, by2);
			rot2(-dd, &dvx, &dvy);
			if (debug >= 4)
			{	printf("Rot z = -atan2(%g, %g) = %g\n", by1, by2, dd);
				rot2(-dd, &by1, &by2);
				rot2(-dd, &bz1, &bz2);
				printf("Y = (%g, %g, %g), Z = (%g, %g, %g)\n", by1, by2, by3, bz1, bz2, bz3);
			}
			rotz = 180.0/M_PI * dd;

			if (debug >= 4)
				printf("New (x, y, z) = (%g, %g, %g)\n", rotx, roty, rotz);
			if ((oflags & OF_DM) != 0)
				model = rmodel;
			goto rd;

		case LeaveNotify:
			goto ns;

		case ResizeRequest:
			swa.override_redirect = True;
			XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &swa);
			XResizeWindow(dpy, win, event.xresizerequest.width, event.xresizerequest.height);
			swa.override_redirect = False;
			XChangeWindowAttributes(dpy, win, CWOverrideRedirect, &swa);
			if (event.xresizerequest.width > xwsiz)
				fillblack(xwsiz, 0, event.xresizerequest.width - xwsiz, ywsiz);
			xwsiz = event.xresizerequest.width;
			if (event.xresizerequest.height > ywsiz)
				fillblack(0, ywsiz, xwsiz, event.xresizerequest.height - ywsiz);
			ywsiz = event.xresizerequest.height;
			break;				/* We'll fix the bitmap the next time we draw into it */
		}
	}
}

/* Calculate bond length, theta, and phi */

void bondparm(struct atm *p1, struct atm *p2, struct bnd *pb)
{
float x, y, z, r, t;

	x = p2->x - p1->x;
	y = p2->y - p1->y;
	z = p2->z - p1->z;
	r = sqrt(x * x + y * y + z * z);
	t = (x == 0.0) ? 90.0 : acos(x / sqrt(x * x + y * y)) * 180.0/M_PI;
	pb->len = r / 2.0;
	pb->theta = (y < 0.0) ? -t: t;
	pb->phi = (r == 0.0) ? 0.0 : acos(z / r) * 180.0/M_PI;
}

/* Comparison function for sorting so we render nearest first */

int cmpf(struct adist *pa, struct adist *pb)
{
	if (pa->r < pb->r) return (-1);
	if (pa->r > pb->r) return (1);
	return (0);
}

/* Calculate scaling from XYZ data only */

float biscale(void)
{
int i;
struct atm *p0, *p1;
float min, best, lim;
float d, f, g;
struct adist *q0, *q1, *qb, *qe;

	qe = patom;
	for (i = atoms + 1; --i > 0; )
	{	p0 = &atom[i];
		qe->pa = p0;
		qe->r = p0->z;		/* Sort on Z position */
		++qe;
	}
	qsort(patom, atoms, sizeof(struct adist), (int (*)(const void *, const void *)) &cmpf);

	min = best = lim = 1e10;
	qb = patom;
	for (q0 = qb; q0 < qe; ++q0)
	{	if ((p0 = q0->pa)->element != E_C)
			continue;
		while (qb->r < q0->r - lim) ++qb;
		for (q1 = qb; q1 < qe; ++q1)
		{	if (q1->r > q0->r + lim)
				break;
			p1 = q1->pa;
			if ((i = p1->element) == E_H)
				f = 1.000;
			else if ((i == E_C) && (q1 > q0))
				f = 0.708;
			else if (i == E_N)
				f = 0.734;
			else if (i == E_O)
				f = 0.758;
			else continue;
			if ((d = (p0->x - p1->x) * f) < 0) d = -d;
			if (d > lim) continue;
			g = d * d;
			if ((d = (p0->y - p1->y) * f) < 0) d = -d;
			if (d > lim) continue;
			g += d * d;
			d = (p0->z - p1->z) * f;
			g += d * d;
			if ((g = sqrt(g)) > lim) continue;
			if (g == 0.0) continue;		/* Yeah, some files are that bad */
			if (g > best) best = g;
			else if (g < min)
			{	lim = g * 1.3;
				if (best > lim)
					best = min;
				min = g;
			}
			else continue;
			if (debug >= 3)
				printf("min = %g, best = %g, (%d to %d)\n", min, best, p0 - atom, p1 - atom);
		}
	}
	if (best > lim)
		best = min;
	return (best);
}

/* Construct bonds from scaled XYZ positions */

void bibond(float s)
{
int n0, n1;
int b0, b1;
struct atm *p0, *p1;
struct adist *q0, *q1;
float d, g, r0, r, mbl;

	s *= 1.28;			/* Margin seems to be from 1.13 to 1.45 */	/***** RECHECK THIS *****/
	mbl = MAX_BOND_LEN * s;
	for (q0 = patom; q0 < &patom[atoms]; ++q0)
	{	r0 = aprops[(p0 = q0->pa)->element].b_radius;
		n0 = p0 - atom;
		for (b0 = 0; b0 < MAXBONDS; ++b0)
			if (p0->bond[b0].toward == 0)
				break;
		for (q1 = q0; ++q1 < &patom[atoms]; )
		{	p1 = q1->pa;
			if ((d = p0->z - p1->z) < 0) d = -d;
			if (d > mbl) break;
			r = (r0 + aprops[p1->element].b_radius) * s;
			if (d > r) continue;
			g = d * d;
			if ((d = p0->x - p1->x) < 0) d = -d;
			if (d > r) continue;
			g += d * d;
			if ((d = p0->y - p1->y) < 0) d = -d;
			if (d > r) continue;
			g += d * d;
			if (g == 0.0) continue;
			if ((g = sqrt(g)) > r) continue;
			n1 = p1 - atom;
			if (g < r * 0.5)
			{	if (debug >= 2)
					printf("Atoms %d and %d too close for bond (distance %g, limit %g)\n",
							n0, n1, g, r * 0.5);
				continue;
			}
			if (b0 >= MAXBONDS)
			{	if (debug >= 2)
					printf("Too many bonds to atom %d\n", n0);
				break;
			}
			for (b1 = 0; b1 < MAXBONDS; ++b1)
				if (p1->bond[b1].toward == 0)
					break;
			if (b1 >= MAXBONDS)
			{	if (debug >= 2)
					printf("Too many bonds to atom %d\n", n1);
				continue;
			}
			p0->bond[b0].toward = n1;
			++b0;
			p1->bond[b1].toward = n0;
		}
	}
}

/* Clean up bond data */

void xyzclean(void)
{
int i, j;
int n0, n1;
int b0, b1;
struct atm *p0, *p1;

	/* Step 1: identify XYZ file type (first "bond" pointer is atom ID with Genome and Tinker) */

	i = atoms;
	for (n0 = 1; n0 <= atoms; ++n0)
	{	p0 = &atom[n0];
		if (((n1 = p0->bond[0].toward) <= 0) || (n1 > atoms))
			continue;
		p1 = &atom[n1];
		for (b1 = MAXBONDS; --b1 >= 0; )
			if (p1->bond[b1].toward == n0)
			{	--i;
				break;
			}
	}
	j = (i * 5 > atoms) ? 1 : 0;	/* Which bond pointer is really the first one? */

	/* Step 2: clean up bad bond pointers */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	p0 = &atom[n0];
		for (b0 = j; b0 < MAXBONDS; ++b0)
		{	if ((n1 = p0->bond[b0].toward) <= 0)
				continue;
			if (n1 == n0)
			{	p0->bond[b0].toward = 0;	/* Atom bonded to itself */
				if (debug >= 2)
					printf("Atom %d bonded to itself (bond %d)\n", n0, b0 - j + 1);
				continue;
			}
			if (n1 > atoms)
			{	p0->bond[b0].toward = 0;	/* Bond pointer out of range */
				if (debug >= 2)
					printf("Bond %d on atom %d goes to atom %d but there are only %d atoms total\n",
							b0 - j + 1, n0, n1, atoms);
				continue;
			}
			p1 = &atom[n1];
			i = 0;
			for (b1 = j; b1 < MAXBONDS; ++b1)
				if	(	(p1->bond[b1].toward == n0)
					&&	(++i > 1)
					)
				{	p1->bond[b1].toward = 0;	/* Redundant */
					if (debug >= 2)
						printf("Redundant bond from atom %d toward %d\n", n1, n0);
				}
			if (i == 0)
			{	p0->bond[b0].toward = 0;		/* Not reciprocal */
				if (debug >= 2)
					printf("Hanging bond from atom %d toward %d\n", n0, n1);
			}
		}
	}

	/* Step 3: compress the remaining bond pointers to start at index 0 */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	p0 = &atom[n0];
		i = -1;
		for (b0 = j; b0 < MAXBONDS; ++b0)
			if ((n1 = p0->bond[b0].toward) > 0)
				p0->bond[++i].toward = n1;
		while (++i < MAXBONDS)
			p0->bond[i].toward = 0;
	}
}

/* Find bond patterns, peptide bonds, alpha carbon chain, water, urea, etc */

void bondpat(void)
{
int i, j;
int n0, n1, n2;
int b0, b1;
struct atm *p0, *p1, *p2;
float pbscale;
int pbnum;

	/* Step 1: calculate "bpat" words for all atoms */

	for (n0 = 1; n0 <= atoms; ++n0)
		for (b0 = MAXBONDS; --b0 >= 0; )
			if ((n1 = atom[n0].bond[b0].toward) > 0)
				atom[n0].bpat += (1 << (atom[n1].element * 3));

	/* Declare a few interesting patterns we will look for */

#define BP_CNO		((1 << (E_C * 3)) + (1 << (E_N * 3)) + (1 << (E_O * 3)))
#define BP_CO		((1 << (E_C * 3)) + (1 << (E_O * 3)))
#define BP_CO2		((1 << (E_C * 3)) + (2 << (E_O * 3)))
#define BP_H2		(2 << (E_H * 3))
#define BP_HMASK	(7 << (E_H * 3))
#define BP_O		(1 << (E_O * 3))
#define BP_N		(1 << (E_N * 3))
#define BP_N2O		((2 << (E_N * 3)) + (1 << (E_O * 3)))
#define BP_C		(1 << (E_C * 3))

	/* Step 2A: find all carbon - carbonyl - imino sequences */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	j = (p0 = &atom[n0])->element;
		if (j == E_H)
			p0->flags |= AF_H;
		if (j != E_C)
			continue;
		n2 = 0;
		p2 = NULL;						/* (just to make the compiler happy) */
		for (b0 = MAXBONDS; --b0 >= 0; )
			if ((n1 = p0->bond[b0].toward) > 0)
			{	if ((j = (p1 = &atom[n1])->element) == E_N)
					n2 = n1;
				else if (j == E_C)
					p2 = p1;
			}
		if ((i = p0->bpat) == BP_CNO)
		{	p0->flags |= AF_CN;			/* Either peptide or amide */
			p2->achc.toward = n2;		/* Temporary pointer from (probably alpha) C to N */
		}
		else if (((i == BP_CO) || (i == BP_CO2)) && (p2->achc.toward <= 0))
			p2->achc.toward = -1;		/* At least eligible to be alpha C */
	}

	/* Step 2B: find imino - alpha-carbon sequences and peptide bonds */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	p0 = &atom[n0];
		if ((n1 = p0->achc.toward) <= 0)	/* Find our temporary pointer */
			continue;
		p0->achc.toward = -1;
		p1 = &atom[n1];
		for (b1 = MAXBONDS; --b1 >= 0; )
		{	if	(	((n2 = p1->bond[b1].toward) <= 0)
				||	((p2 = &atom[n2])->achc.toward == 0)
				) continue;
			p0->achc.toward = n2;			/* Peptide bond, carbonyl side */
			p2->acha.toward = n0;			/* Peptide bond, imino side */
		}
	}

	/* Step 2C: calculate angles and flush isolated peptide bonds */

	pbscale = 0.0;
	pbnum = 0;
	for (n0 = 1; n0 <= atoms; ++n0)
	{	p0 = &atom[n0];
		if ((n2 = p0->achc.toward) <= 0)
			goto dc;
		p2 = &atom[n2];
		if	(	(p0->acha.toward == 0)
			&&	(p2->achc.toward == 0)
			)
		{	p2->acha.toward = 0;			/* Isolated peptide bond, not in "backbone" */
dc:			p0->achc.toward = 0;
			continue;
		}
		atom[n0].flags |= AF_CA;			/* Mark both as alpha Carbon */
		atom[n2].flags |= AF_CA;
		bondparm(p0, p2, &p0->achc);		/* Calculate angles */
		bondparm(p2, p0, &p2->acha);
		if (debug >= 3)
		{	for (b0 = MAXBONDS; --b0 >= 0; )
				if	(	((n1 = p0->bond[b0].toward) > 0)
					||	(atom[n1].element == E_N)
					)
				{	printf("Alpha C1 #%d, C2 #%d\n", n0, n2);
					goto nb;
				}
			printf("No amino alpha C1 #%d, alpha C2 #%d\n", n0, n2);
		}
nb:		for (b0 = MAXBONDS; --b0 >= 0; )			/* Mark atoms in backbone */
		{	if	(	((n1 = p2->bond[b0].toward) > 0)
				&&	((p1 = &atom[n1])->element == E_N)
				)							/* p1 is peptide N */
			{	p0 = NULL;
				for (b1 = MAXBONDS; --b1 >= 0; )
				{	if ((n1 = p1->bond[b1].toward) <= 0)
						continue;
					atom[n1].flags |= AF_BB;			/* Mark if bonded to peptide N */
					if ((atom[n1].flags & AF_CN) != 0)	/* Already identified as carbonyl or amide */
						p0 = &atom[n1];		/* p0 is peptide C */
				}
				if (p0 != NULL)
				{	pbscale += sqrt((p1->x - p0->x) * (p1->x - p0->x) +
							(p1->y - p0->y) * (p1->y - p0->y) + (p1->z - p0->z) * (p1->z - p0->z));
					pbnum++;
					for (b1 = MAXBONDS; --b1 >= 0; )
						if ((n1 = p0->bond[b1].toward) > 0)
							atom[n1].flags |= AF_BB;	/* Mark if bonded to peptide C */
				}
			}
		}
	}
	if ((debug >= 2) && (pbnum > 0))
	{	pbscale /= (float) pbnum * scale;
		printf("Average C-N in peptide bond: %g (%.2f scale)\n", pbscale, pbscale / 1.33);
	}

	/* Step 3: identify water molecules */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	if	(	((p0 = &atom[n0])->bpat != BP_H2)
			||	(p0->element != E_O)
			)
nw:			continue;
		for (b0 = MAXBONDS; --b0 >= 0; )
			if	(	((n1 = p0->bond[b0].toward) > 0)
				&&	(atom[n1].bpat != BP_O)
				) goto nw;
		p0->flags |= AF_H2O;				/* Mark the Oxygen */
		for (b0 = MAXBONDS; --b0 >= 0; )
		{	if ((n1 = p0->bond[b0].toward) > 0)
				atom[n1].flags |= AF_H2O;	/* Mark the Hydrogens */
		}
		++nh2o;
	}

	/* Step 4A: mark all amino nitrogens */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	if	(	((p0 = &atom[n0])->element != E_N)
			||	((p0->bpat & BP_HMASK) != BP_H2)
			)
na:			continue;
		for (b0 = MAXBONDS; --b0 >= 0; )
			if	(	((n1 = p0->bond[b0].toward) > 0)
				&&	(atom[n1].element == E_H)
				&&	(atom[n1].bpat != BP_N)
				) goto na;
		p0->flags |= AF_NNH2;				/* Mark it */
	}

	/* Step 4B: identify urea molecules */

	for (n0 = 1; n0 <= atoms; ++n0)
	{	if	(	((p0 = &atom[n0])->bpat != BP_N2O)
			||	(p0->element != E_C)
			)
nu:			continue;
		for (b0 = MAXBONDS; --b0 >= 0; )
			if	(	((n1 = p0->bond[b0].toward) > 0)
				&&	(	(	(atom[n1].element == E_O)
						&&	(atom[n1].bpat != BP_C)
						)
					||	(	(atom[n1].element == E_N)
						&&	((atom[n1].flags & AF_NNH2) == 0)
						)
					)
				) goto nu;
		for (b0 = MAXBONDS; --b0 >= 0; )
		{	if ((n1 = p0->bond[b0].toward) <= 0)
				continue;
			(p1 = &atom[n1])->flags |= AF_UREA;	/* Mark Nitrogens and Oxygen */
			for (b1 = MAXBONDS; --b1 >= 0; )
				if ((n2 = p1->bond[b1].toward) > 0)
					atom[n2].flags |= AF_UREA;	/* Mark everything else */
		}
		++nurea;
	}
}

/* Read in the XYZ file data for the molecule to display */

int readxyz(void)
{
int i, j, k, n;
char *p, *q;
FILE *fp;
struct atm *pa;
int na;
int zi, zn, zz;
float xa, xb, ya, yb, za, zb;
float chdst;
float d, g;
struct stat st;

	p = ETYPES;
	if (stat(xyzname, &st) != 0)
		goto co;
	if (st.st_mtime == xyztime)
		return (0);				/* File hasn't changed since last time */
	if ((fp = fopen(xyzname, "r")) == NULL)
	{
co:		sprintf(errmsg, "Can't open XYZ file \"%.160s\"", xyzname);
		return (1);
	}
	na = 0;
	if (fgets(wbuf, sizeof(wbuf), fp) != NULL)
	{	pname[0] = '\0';
		pname[30] = '\0';
		sscanf(wbuf, "%d %30c", &na, pname);
		for (i = 0; i < 30; ++i)
			if (((j = pname[i]) == '\012') || (j == '\015'))
			{	pname[i] = '\0';
				break;
			}
	}
	if (pname[0] == '\0')	/* Genome */
		strcpy(pname, "Genome");

	nh2o = 0;
	nurea = 0;
	n = 0;
	atoms = 0;
	++na;								/* Tinker reports one too few atoms */
	if (na >= MAXATOMS)
	{	sprintf(errmsg, "Molecule too large.  Max = %d atoms", MAXATOMS);
		n = 2;
		goto cf;
	}
	if (atom != NULL)
		free(atom);
	i = sizeof(struct atm) * (na + 3);
	j = i + sizeof(struct adist) * (na + 3);
	if ((atom = malloc(j)) == NULL)
	{	sprintf(errmsg, "Can't assign memory.  Need %d bytes.", j);
		n = 3;
		goto cf;
	}
	memset(atom, 0, j);					/* Zeroing the array is important */
	patom = (struct adist *) ((char *) atom + i);
	pa = &atom[1];						/* Atom 0 isn't used */

	while (fgets(wbuf, sizeof(wbuf), fp) != NULL)
	{	if (sscanf(wbuf, "%d%7s%f%f%f%n", &zi, ebuf, &pa->x, &pa->y, &pa->z, &zn) < 5)
			continue;
		if ((atoms = pa - atom) != zi)
		{	if (atoms >= na) break;		/* Don't complain if last atom */
			sprintf(errmsg, "Sequence error in XYZ file expecting atom number %d", atoms);
			n = 4;
			break;
		}
		pa->element = ((q = strchr(p, ebuf[0])) == NULL) ? 0 : (q - p);
		if ((oflags & OF_BI) == 0)
		{	zi = 0;
			for (i = 0; ; ++i)
			{	zn += zi;
				if (sscanf(&wbuf[zn], "%d%n", &zz, &zi) != 1)
					break;
				if (i >= MAXBONDS)			/* Too much garbage */
				{	sprintf(errmsg, "Too many fields in XYZ file at atom number %d", pa - atom);
					n = 5;
					goto cf;
				}
				pa->bond[i].toward = zz;
			}
		}
		if (atoms >= na) break;			/* End of atom list, according the the header line */
		++pa;
	}
cf:	fclose(fp);
	if (n != 0)
		return (n);

	if (atoms == 0)
	{	sprintf(errmsg, "No atoms in molecule");
		return (6);
	}

	if ((oflags & OF_BI) == 0)
		xyzclean();			/* Clean up bond data */

	g = 0.0;			/* Calculate scaling */
	chdst = xa = ya = za = 1e10;
	xb = yb = zb = -1e10;
	for (i = atoms + 1; --i > 0; )
	{	pa = &atom[i];
		if (xa > pa->x) xa = pa->x;
		if (xb < pa->x) xb = pa->x;
		if (ya > pa->y) ya = pa->y;
		if (yb < pa->y) yb = pa->y;
		if (za > pa->z) za = pa->z;
		if (zb < pa->z) zb = pa->z;
		if ((oflags & OF_BI) != 0)
			continue;
		for (j = MAXBONDS; --j >= 0; )
		{	if ((k = pa->bond[j].toward) <= 0)
				continue;
			if (pa->element * 10 + atom[k].element != CH_BOND_ID)
				continue;				/* Just look at lengths of C-H bonds */
			bondparm(pa, &atom[k], &pa->bond[j]);	/* Temporarily calculate len */
			g += pa->bond[j].len;
			d = g * 1.2 / (float) ++n;
			if (d > 1.44 * chdst)
				g = chdst * (float) n;
			if	(	(chdst > d)
				||	(	(pa->bond[j].len > chdst)
					&&	(pa->bond[j].len < d)
					)
				) chdst = pa->bond[j].len;		/* Best guess for "typical" C-H bond */
		}
	}
	if ((oflags & OF_BI) == 0)
		chdst *= 2;
	if (chdst > 1e5)					/* Either no C-H bonds, or we've been told to calculate it all */
		chdst = biscale();
	if (chdst > 1e5)					/* Not a reasonable value, set to default */
		chdst = CH_BOND_LEN;

	chdst *= 1.0177;					/* This seems to give the best match in peptide bonds */

	xa = (xb + xa) / 2;
	xb -= xa;
	ya = (yb + ya) / 2;
	yb -= ya;
	za = (zb + za) / 2;
	zb -= za;
	if (xb < yb) xb = yb;
	if (xb < zb) xb = zb;
	xb = 1 / xb;
	scale = xb * chdst / CH_BOND_LEN;

	for (i = atoms + 1; --i > 0; )
	{	pa = &atom[i];
		pa->x = (pa->x - xa) * xb;		/* Adjust to fit in box (-1, -1, -1) to (+1, +1, +1) */
		pa->y = (pa->y - ya) * xb;
		pa->z = (pa->z - za) * xb;
	}
	if ((oflags & OF_BI) != 0)
		bibond(scale);					/* Scaling seems to work from 1.13 to 1.45 */
	for (i = atoms + 1; --i > 0; )
	{	pa = &atom[i];
		for (j = MAXBONDS; --j >= 0; )
		{	if ((k = pa->bond[j].toward) <= 0)
				continue;
			bondparm(pa, &atom[k], &pa->bond[j]);	/* Calculate len, theta, phi */
			d = pa->bond[j].len * 2.0 / scale;
			g = (aprops[pa->element].b_radius + aprops[atom[k].element].b_radius) * 1.46;
			if (d > g)
			{	pa->bond[j].toward = 0;		/* Something is wrong, bond is too long */
				if ((debug >= 2) && (i < k))
					printf("Bond from atom %d to atom %d too long (length %g, limit %g)\n", i, k, d, g);
			}
			if	(	(debug >= 4)
				&&	(pa->element * 10 + atom[k].element == CH_BOND_ID)
				) printf("C-H scaled bond length %g (%d to %d)\n", d, i, k);
		}
	}

	bondpat();		/* Find peptide bonds and set up the alpha carbon chain */

	if (debug >= 1)
	{	printf("Atom size scale %g, molecule scale %g\npname = \"%s\", atoms = %d\n",
				scale / xb, scale, pname, atoms);
		if (nh2o != 0)
			printf("Number of water molecules = %d\n", nh2o);
		if (nurea != 0)
			printf("Number of urea molecules = %d\n", nurea);
	}
	if (debug >= 5)
		for (i = 1; i <= atoms; ++i)
		{	pa = &atom[i];
			printf(" Atom %03d, element %d (%c): (%g, %g, %g), bonds:", i,
					pa->element, p[pa->element], pa->x, pa->y, pa->z);
			for (na = 0; ; na = k)
			{	k = atoms + 1;
				for (j = 0; j < MAXBONDS; ++j)
					if	(	((n = pa->bond[j].toward) > na)
						&&	(n < k)
						) k = n;
				if (k > atoms) break;
				printf(" %d", k);
			}
			if ((pa->flags & AF_H2O) != 0)
				printf(" H2O");
			printf("\n");
		}
	xyztime = st.st_mtime;
	return (0);
}

/* Display an error message into the pixmap */

void demsg(char *h, char *m)
{
int a, b, y;
int i, k, n;

	XSetForeground(dpy, gc, 0);
	XFillRectangle(dpy, glwin, gc, 0, 0, xsiz, ysiz);
	y = fsch * 2;
	XSetForeground(dpy, gc, gray80);
	XDrawString(dpy, glwin, gc, fscw / 2, y, h, strlen(h));
	y += (fsch * 3) / 2;
	k = xsiz / fscw - 1;
	a = 2;
	for (i = strlen(m); i > 0; y += fsch)
	{	b = 2;
		if (i <= k - a)
			n = i;
		else
		{	for (n = k - a; (n > 2) && (m[n] != ' '); --n) ;
			if (n < 3)
			{	n = k - a;
				b = 0;
			}
		}
		if (n <= 0) break;		/* Good grief! */
		XDrawString(dpy, glwin, gc, fscw * a + fscw / 2, y, m, n);
		m += n;
		i -= n;
		a = b;
	}
}

/* Initialize OpenGL environment */

float front_specular[] = { 0.6, 0.6, 0.6, 1.0 };
float ambient0[] = { 0.3, 0.3, 0.3, 1.0 };
float diffuse0[] = { 1.0, 1.0, 1.0, 1.0 };
float position0[] = { 1.5, 1.5, 1.5, 0.0 };
float ambient1[] = { 0.15, 0.15, 0.15, 1.0 };
float diffuse1[] = { 0.5, 0.5, 0.5, 1.0 };
float position1[] = { -1.5, -1.5, 1.5, 0.0 };
float lmodel_ambient[] = { 0.5, 0.5, 0.5, 1.0 };
float lmodel_twoside[] = { GL_TRUE };

void InitGL(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);	/* Doesn't hurt, nice also if green is 0.3 */
	glClearDepth(1.0);					/* Doesn't hurt */
	glDepthFunc(GL_LESS);				/* Doesn't hurt */
	glEnable(GL_DEPTH_TEST);
#if 0		/* Modes which might someday be useful, but now just make it slower */
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_POINT_SMOOTH);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
#endif

	/* Lighting */

	glLightfv(GL_LIGHT0, GL_AMBIENT, ambient0);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse0);
	glLightfv(GL_LIGHT0, GL_POSITION, position0);
	glLightfv(GL_LIGHT1, GL_AMBIENT, ambient1);
	glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse1);
	glLightfv(GL_LIGHT1, GL_POSITION, position1);
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
	glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHT1);

	glEnable(GL_NORMALIZE);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glShadeModel(GL_SMOOTH);

	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, front_specular);
	glDrawBuffer(GL_FRONT_LEFT);	/* Change to GL_FRONT_AND_BACK for animation */

	/* Window size */

	glViewport(0, 0, xsiz, ysiz);
}

int dobond(struct bnd *pb, int n)
{
	if	(	(pb->toward <= 0)				/* No bond */
		||	(	((oflags & OF_IH) != 0)		/* Don't display bonds to hydrogen */
			&&	((atom[pb->toward].flags & AF_H) != 0)	/* and it's to hydrogen */
			)
		) return (0);
	glPushMatrix();
	glRotatef(pb->theta, 0, 0, 1);
	glRotatef(pb->phi, 0, 1, 0);
#if NOGLU
	dCylinder(bond_radius, pb->len, n);
#else
	gluCylinder(qobj, bond_radius, bond_radius, pb->len, n, 1);
#endif
	if ((atom[pb->toward].flags & AF_DL) == 0)	/* The atom it's bonded to isn't being displayed */
	{	glTranslatef(0.0, 0.0, pb->len);	/* Cap it so we don't see "inside" the bond */
#if NOGLU
		dSphere(bond_radius, n);
#else
		gluSphere(qobj, bond_radius, n, n);
#endif
	}
	glPopMatrix();
	return (1);
}

/* Render the molecule */

void draw_molecule(void)
{
int i, j, n;
struct atm *pa;
struct adist *pd, *pe;
struct atmprop *pp;
float sscale, d, g, h;
float dqual;
float shiny;

	glPushMatrix();
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glRotatef(rotx, 1.0, 0.0, 0.0);
	glRotatef(roty, 0.0, 1.0, 0.0);
	glRotatef(rotz, 0.0, 0.0, 1.0);

	/* Set feature scales suitable for the model type */

	sscale = 1.0;
	bond_radius = BOND_RADIUS * scale;
	if (model == MB_BS)			/* Ball and Stick */
		sscale = sldtab[2].val;
	else if (model == MB_AT)	/* Alpha Carbon Trace */
		bond_radius = ALPHA_BOND_RADIUS * scale;
	else if (model == MB_WF)	/* Wire Frame */
		sscale = 0.0;
	sscale *= scale;
	dqual = DQUAL * pow(1.2, (double) sldtab[1].val) * FOV / fov;

	/* Select the atoms to be displayed and sort the list so closest are first */

	pe = patom;
	for (i = atoms + 1; --i > 0; )
	{	pa = &atom[i];
		pa->flags &= ~AF_DL;
		if	(	(	((oflags & OF_BO) != 0)		/* Display backbone only */
				&&	((pa->flags & AF_BB) == 0)	/* This isn't backbone */
				)
			||	(	((oflags & OF_IH) != 0)		/* Don't display hydrogen */
				&&	((pa->flags & AF_H) != 0)	/* This is hydrogen */
				)
			||	(	((oflags & OF_IW) != 0)		/* Don't display water */
				&&	((pa->flags & AF_H2O) != 0)	/* This is water */
				)
			||	(	((oflags & OF_IU) != 0)		/* Don't display urea */
				&&	((pa->flags & AF_UREA) != 0)	/* This is urea */
				)
			||	(	(model == MB_AT)			/* Alpha Trace model */
				&&	(pa->achc.toward == 0)		/* No bond toward carboxyl end */
				&&	(pa->acha.toward == 0)		/* No bond toward amino end */
				)
			) continue;				/* Skip this atom */
		pe->pa = pa;
		d = pa->x - ox - dvx * zdist;
		g = d * d;
		h = d * dvx;
		d = pa->y - oy - dvy * zdist;
		g += d * d;
		h += d * dvy;
		d = pa->z - oz - dvz * zdist;
		g += d * d;
		h += d * dvz;
		pe->r = sqrt(g);
		h = -h / pe->r;		/* Cosine of the angle off the direction of view */
		if	(	(h < 0)
			||	(	(h < 0.75)
				&&	(pe->r > sscale * 10 * MAX_BOND_LEN)
				)
			) continue;		/* Atom and its bonds are out of the field of view */
		if (pe->r < nearest / h + sscale * aprops[pa->element].d_radius)
			continue;		/* Atom is too close to display properly */
		pa->flags |= AF_DL;
		++pe;
	}
	if (debug >= 2)
		printf("Atoms in display list: %d\n", pe - patom);
	qsort(patom, pe - patom, sizeof(struct adist), (int (*)(const void *, const void *)) &cmpf);

	/* Display each atom in sorted list */

	for (pd = patom; pd < pe; ++pd)
	{	pa = pd->pa;
		if (model == MB_AT)
		{	if (pa->achc.toward == 0)
				pp = &acprops[2];	/* Special color for carboxyl end */
			else if (pa->acha.toward == 0)
				pp = &acprops[1];	/* Special color for amino end */
			else
				pp = &acprops[0];
		}
		else
			pp = &aprops[pa->element];
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, pp->color);
		glPushMatrix();
		glTranslatef(pa->x - ox, pa->y - oy, pa->z - oz);
		h = dqual / pd->r;
		if (model == MB_SF)
			goto ds;
		d = 3.0 * sqrt(h * bond_radius);
		if ((n = (int) d) < 8) n = 8;
		if (n > 32) n = 32;
		shiny = (float) n * 4.0 - 15.0;
		glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &shiny);
		if (model == MB_AT)
		{	dobond(&pa->achc, n);
			dobond(&pa->acha, n);
			goto ds;
		}
		else
		{	j = 0;
			for (i = MAXBONDS; --i >= 0; )
				j += dobond(&pa->bond[i], n);
			if ((j == 0) && (model == MB_WF))
				goto ns;
ds:			g = sscale * pp->d_radius;
			if ((model != MB_SF) && (g < bond_radius))
				g = bond_radius;
			d = sqrt(g * h);
			if ((n = (int) d) < 10) n = 10;
			if (n > 48) n = 48;
			shiny = (float) n * 4.0 - 15.0;
			glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &shiny);
#if NOGLU
			dSphere(g, n);
#else
			gluSphere(qobj, g, n, n);
#endif
		}
ns:		glPopMatrix();
#if NOPMAP
		if (XCheckIfEvent(dpy, &event, WaitForExpose, (char *) win))
		{	XPutBackEvent(dpy, &event);
			break;
		}
#endif
	}

	glPopAttrib();
	glPopMatrix();
}

/* Render an OpenGL image of the molecule */

void draw_scene(void)
{
float d, e, g;
float aspect;
float sdep;
#if !NOPMAP
int i;
Pixmap opixmap;
GLXPixmap opmap;
#endif
#if DLIST
GLint dlist;
#endif
double cplane[4];

	if (((oflags ^ coflags) & OF_BI) != 0)
		xyztime = 0;
	cmodel = model;
	czoom = zoom;
	cstflg = menu[MB_ST].flags;
	csdctr = sldtab[0].val;
	cdqctr = sldtab[1].val;
	cbsbsiz = sldtab[2].val;
	coflags = oflags;

	/* Make sure the current pixmap is the right size for the screen */

	if	(	(ywsiz != ytop + ysiz)
		||	(xwsiz != xsiz)
		)					/* Assign a new pixmap */
	{	xsiz = xwsiz;
		ysiz = ywsiz - ytop;
#if NOPMAP
		glXMakeCurrent(dpy, win, cx);
#else
		opixmap = pixmap;
		opmap = pmap;
		pixmap = XCreatePixmap(dpy, win, xsiz, ysiz, vi->depth);
		glwin = (Window) pixmap;
		pmap = glXCreateGLXPixmap(dpy, vi, pixmap);
		glXMakeCurrent(dpy, pmap, cx);
		if (opixmap != 0)	/* Free the old pixmap after the new one is made current */
		{	glXDestroyGLXPixmap(dpy, opmap);
			XFreePixmap(dpy, opixmap);
		}
#endif
		InitGL();
	}

	if (readxyz() != 0)				/* Read in the XYZ file data */
	{	demsg("Error reading XYZ file:", errmsg);
		return;
	}

#if !NOPMAP
	i = ytop + ysiz / 8;
	XSetForeground(dpy, gc, gray30);
	XFillRectangle(dpy, win, gc, xwsiz / 2 - 6 * fscw, i - fsbh / 2, 12 * fscw, fsbh);
	XSetForeground(dpy, gc, orange);
	XDrawString(dpy, win, gc, xwsiz / 2 - 5 * fscw, i + fsch / 3, "WORKING...", 10);
	XFlush(dpy);
#endif
	dotitle();
#if DLIST
	dlist = glGenLists(1);
	glNewList(dlist, GL_COMPILE);
#endif

	/* Set perspective based on scaling and current view */

	sdep = SDEP / pow(DEPF, (double) sldtab[0].val);
	if ((oflags & OF_RL) == 0)
		sdep *= 1.5;						/* Viewpoint is relatively closer to the screen in [R L] mode */
	aspect = (float) xsiz / (float) ysiz;
	fov = FOV;
	zdist = zoom;
	if ((cstflg & BF_T) != 0)	/* Stereo */
	{	fov /= sdep;
		zdist *= sdep * 1.3;
	}
	e = 1.732 + LARGEST_ATOM * scale;		/* This adjustment should really be in readxyz */
	d = zdist + e;
	g = sqrt(d * d + e);
	if ((d /= cos(fov * M_PI/360.0)) < g) g = d;
	if ((d = zdist - e) < g * 0.01) d = g * 0.01;
	if (debug >= 1)
		printf("zoom = %g, zdist = %g, near = %g, far = %g\n", zoom, zdist, d, g);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	e = d * tan(fov * M_PI/360.0 / sqrt(aspect));
	glFrustum(-e * aspect, e * aspect, -e, e, d, g);
	yspan = 2.0 * e * sqrt(g / d);
	xspan = yspan * aspect;
	nearest = d;
	glMatrixMode(GL_MODELVIEW);

	/* Clear the bitmap and render the requested image */

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	if ((cstflg & BF_T) != 0)	/* Stereo */
	{	g = -(zdist / sdep) * sqrt(aspect) * SSEP * FOV * M_PI/180.0 / 2.0;
		d = ((oflags & OF_RL) == 0) ? fov * SSEP : 0.0;		/* Select stereo mode */
		cplane[1] = cplane[2] = cplane[3] = 0.0;
		glEnable(GL_CLIP_PLANE0);
		glPushMatrix();
		cplane[0] = 1.0;
		glClipPlane(GL_CLIP_PLANE0, cplane);
		glTranslatef(-g, 0.0, -zdist);
		glRotatef(-d, 0.0, 1.0, 0.0);
		draw_molecule();
		glPopMatrix();
		cplane[0] = -1.0;
		glClipPlane(GL_CLIP_PLANE0, cplane);
		glTranslatef(g, 0.0, -zdist);
		glRotatef(d, 0.0, 1.0, 0.0);
		draw_molecule();
		glDisable(GL_CLIP_PLANE0);
	}
	else
	{	glTranslatef(0.0, 0.0, -zdist);
		draw_molecule();
	}
#if DLIST
	glEndList();
	glCallList(dlist);
	glDeleteLists(dlist, 1);
#endif
	glXWaitGL();
	if ((cstflg & BF_T) != 0)	/* Stereo */
	{	XSetForeground(dpy, gc, yellow);
#if NOPMAP
		XDrawLine(dpy, glwin, gc, M_LW / 2, ytop, M_LW / 2, ysiz + ytop);
		XDrawLine(dpy, glwin, gc, xsiz / 2, ytop, xsiz / 2, ysiz + ytop);
		XDrawLine(dpy, glwin, gc, xsiz - M_LW / 2, ytop, xsiz - M_LW / 2, ysiz + ytop);
#else
		XDrawLine(dpy, glwin, gc, M_LW / 2, 0, M_LW / 2, ysiz);
		XDrawLine(dpy, glwin, gc, xsiz / 2, 0, xsiz / 2, ysiz);
		XDrawLine(dpy, glwin, gc, xsiz - M_LW / 2, 0, xsiz - M_LW / 2, ysiz);
#endif
	}
}

/* Queue information display functions, adapted from "qd" */

#define be4(x)	((x[0]<<24)|((x[1]&0xFF)<<16)|((x[2]&0xFF)<<8)|(x[3]&0xFF))
#define le4(x)	((x[3]<<24)|((x[2]&0xFF)<<16)|((x[1]&0xFF)<<8)|(x[0]&0xFF))
#define le2(x)	(((x[1]&0xFF)<<8)|(x[0]&0xFF))
#define exch(x, y, t)	{t=x;x=y;y=t;}

/* Structure of <queue.dat> file */

typedef unsigned int u32;
typedef unsigned short u16;

struct qf
{	u32		version;	/* 0000 Queue (client) version (v2.17 and above) */
	u32		current;	/* 0004 Current index number */
	struct qs
	{	u32		stat;		/* 000 Status */
		char	z004[4];	/* 004 Pad for Windows, others as of v4.01 */
		u32		tdata[8];	/* 008 Time data (epoch 0000 1jan00 GMT) */
		u32		svr1;		/* 040 Server IP address (until v3.0) */
		u32		ustat;		/* 044 Upload status */
		char	url[128];	/* 048 Web address for core downloads */
		u32		m176;		/* 176 Misc1a */
		u32		core;		/* 180 Core_xx number (hex) */
		u32		m184;		/* 184 Misc1b */
		u32		dsiz;		/* 188 wudata_xx.dat file size */
		char	z192[16];
		union
		{	struct
			{	char	proj[2];	/* 208 Project number (LE) */
				char	run[2];		/* 210 Run (LE) */
				char	clone[2];	/* 212 Clone (LE) */
				char	gen[2];		/* 214 Generation (LE) */
				char	issue[2][4];	/* 216 WU issue time (LE) */
			}		f;			/* Folding@home data */
			struct
			{	char	proj[2];	/* 208 Project number (LE) */
				u16		miscg1;		/* 210 Miscg1 */
				char	issue[2][4];	/* 212 WU issue time (LE) */
				u16		miscg2;		/* 220 Miscg2 */
				u16		miscg3;		/* 222 Miscg3 */
			}		g;			/* Genome@home data */
		}		wuid;		/* 208 Work unit ID information */
		char	z224[36];
		char	mid[4];		/* 260 Machine ID (LE) */
		u32		svr2;		/* 264 Server IP address */
		u32		port;		/* 268 Server port number */
		char	type[64];	/* 272 Work unit type */
		char	uname[64];	/* 336 User Name */
		char	teamn[64];	/* 400 Team Number */
		char	uid[8];		/* 464 Stored ID for unit (UserID + MachineID) */
		char	bench[4];	/* 472 Benchmark (as of v3.24) (LE) */
		char	m476[4];	/* 476 Misc3b (unused as of v3.24) (LE) */
		char	z480[16];
		u32		expire;		/* 496 Allowed time to return (seconds) */
		char	z500[8];
		char	aiflag[4];	/* 508 Assignment info present flag (LE or BE) */
		char	aitime[4];	/* 512 Assignment timestamp (LE or BE) */
		char	aidata[4];	/* 516 Assignment info (Ax xx xx xx) (LE or BE) */
		char	csip[4];	/* 520 Collection server IP address (as of v5.00) (LE) */
		char	dstart[4];	/* 524 Download started time (as of v5.00) (BE) */
		char	z528[160];
		u32		due[4];		/* 688 WU expiration time */
		u32		plimit;		/* 704 Packet size limit (as of v5.00) */
		u32		uploads;	/* 708 Number of upload failures (as of v5.00) */
	}		entry[10];	/* 0008 Array of ten queue entries */
	u32		pfract;		/* 7128 Performance fraction (as of v3.24) */
	u32		punits;		/* 7132 Performance fraction unit weight (as of v3.24) */
	u32		drate;		/* 7136 Download rate sliding average (as of v4.00) */
	u32		dunits;		/* 7140 Download rate unit weight (as of v4.00) */
	u32		urate;		/* 7144 Upload rate sliding average (as of v4.00) */
	u32		uunits;		/* 7148 Upload rate unit weight (as of v4.00) */
	char	z7152[16];	/* 7152 (as of v5.00) ...all zeros after queue conversion... */
} qbuf;

/* Print time and date */

void prtime(char *pf, char *p, u32 t)
{
time_t a;

	if (t == 0)
	{	sprintf(pf, "%sZERO", p);
		return;
	}
	a = t + TIME_OFS;			/* Adjust epoch, "ctime" will do time zone */
	sprintf(pf, "%s%s", p, ctime(&a));
	pf[strlen(pf) - 1] = '\0';	/* Get rid of the newline */
}

u32 es32(u32 i)
{
	return ((i << 24) | ((i & 0xFF00) << 8)
			| ((i & 0xFF0000) >> 8) | ((i & 0xFF000000) >> 24));
}

void eswp(struct qf *bp)	/* Swap endianness of entire queue file */
{
int i, n;
struct qs *p;

	bp->version = es32(bp->version);
	bp->current = es32(bp->current);
	for (n = 10; --n >= 0; )
	{	p = &bp->entry[n];
		if (qver < 324)
			p = (struct qs *) ((char *) p - 16 * n);
		if (qver < 500)
			p = (struct qs *) ((char *) p - 8 * n);
		if ((qver < 401) && (systype != 1))
			p = (struct qs *) ((char *) p - 4 * n);
		p->stat = es32(p->stat);
		if ((qver < 401) && (systype != 1))
			p = (struct qs *) ((char *) p - 4);
		p->tdata[0] = es32(p->tdata[0]);
		p->tdata[1] = es32(p->tdata[1]);
		p->tdata[2] = es32(p->tdata[2]);
		p->tdata[3] = es32(p->tdata[3]);
		p->tdata[4] = es32(p->tdata[4]);
		p->tdata[5] = es32(p->tdata[5]);
		p->tdata[6] = es32(p->tdata[6]);
		p->tdata[7] = es32(p->tdata[7]);
		p->svr1 = es32(p->svr1);
		p->ustat = es32(p->ustat);
		p->m176 = es32(p->m176);
		p->core = es32(p->core);
		p->m184 = es32(p->m184);
		p->dsiz = es32(p->dsiz);
		if (((i = p->core) & 0xFF) == 0)
			i = es32(i);
		p->svr2 = es32(p->svr2);
		p->port = es32(p->port);
		p->expire = es32(p->expire);
		if (qver >= 324)
		{	p->due[0] = es32(p->due[0]);
			p->due[1] = es32(p->due[1]);
			p->due[2] = es32(p->due[2]);
			p->due[3] = es32(p->due[3]);
		}
		if (qver >= 500)
		{	p->uploads = es32(p->uploads);
			p->plimit = es32(p->plimit);
		}
	}
	if (qver < 500)
		bp = (struct qf *) ((char *) bp - 80);
	if (qver >= 324)
	{	bp->pfract = es32(bp->pfract);
		bp->punits = es32(bp->punits);
	}
	if (qver >= 400)
	{	bp->drate = es32(bp->drate);
		bp->dunits = es32(bp->dunits);
		bp->urate = es32(bp->urate);
		bp->uunits = es32(bp->uunits);
	}
}

void qd(void)
{
int f, i, j, n;
int estat;
bool gf;
struct qs *p;
struct qf *bp;
u32 *tp, *dp;
char *q;
int na, nb, nc;
int za;
char zc;
int proj;
FILE *fp;
struct sbtn *ps;
char *pf;
float fc, fcl;
int tcl, tcc;
struct stat st;

	ps = qiscreen;
	ps->flags = BF_B;
	pf = qibuf;
	if ((fp = fopen(qname, "rb")) == NULL)
		goto co;
	i = fread(&qbuf, 1, sizeof(struct qf), fp);
	fclose(fp);
	if (i < 6844)
	{
co:		sprintf(pf,"Can't read <%.150s> file", qname);
		ps->id = -1;
		ps->text = pf;
		pf += strlen(pf) + 1;
		goto rt;
	}
	if ((oflags & OF_QT) == 0)
	{	gf = (qbuf.version > 0xFFFF)
				&& ((qbuf.current > 0xFFFF) || (qbuf.current == 0));
		qver = 0;
		switch (i)		/* Determine system type, mainly from file length */
		{
		case 6884:		/* 2.15W - 2.17W */
			qver = 217;
			goto tw;
		case 6888:		/* 3.00W - 3.14W */
			qver = 314;
			goto tw;
		case 7048:		/* 3.24W earlier */
		case 7056:		/* 3.24W later */
			qver = 324;
tw:			systype = 1;		/* Windows */
			break;
		case 6848:		/* 2.19L (3.12M) - 3.14LM */
			qver = 314;
			goto tl;
		case 7008:		/* 3.24LM earlier */
		case 7016:		/* 3.24LM later */
		case 7032:		/* 4.00L */
			qver = 324;
tl:			if (gf)			/* Wrong endianness */
#if (SYSTYPE != 2)
				systype = 2;	/* Mac */
			else
#endif
				systype = 0;	/* Linux */
			break;
		case 7072:		/* 4.00W, 4.01LWM */
			qver = 401;
			goto tt;
		case 7168:		/* 5.00LWM */
			qver = 500;
tt:			if (gf)			/* Wrong endianness */
#if (SYSTYPE == 2)
				systype = 1;	/* Linux or Windows */
#else
				systype = 2;	/* Mac */
			else if (qbuf.version == 400)
				systype = 1;	/* Windows */
#endif
			break;
		}
	}
#if (SYSTYPE == 2)
	if (systype != 2)
		eswap = TRUE;
#else
	if (systype == 2)
		eswap = TRUE;
#endif

	i = qbuf.version;
	if (eswap) i = es32(i);
	if (i > 9)
	{	bp = &qbuf;
		if (i > MAXQVER)
		{	if (qver == 0)
				qver = DEFQVER;
		}
		else
			qver = i;
	}
	else
	{	bp = (struct qf *) ((char *) &qbuf - 4);
		qver = 0;
	}
	if (eswap)
		eswp(&qbuf);

	if (qiselect < 0)
		qiselect = QB_QD + (bp->current & 0xFF) % 10;
	f = 1;
	for (i = 10; --i >= 0; )
	{	n = ((bp->current & 0xFF) - i + 10) % 10;		/* Print oldest first */
		p = &bp->entry[n];
		if (qver < 324)
			p = (struct qs *) ((char *) p - 16 * n);
		if (qver < 500)
			p = (struct qs *) ((char *) p - 8 * n);
		if ((qver < 401) && (systype != 1))
			p = (struct qs *) ((char *) p - 4 * n);
		estat = p->stat;
		if ((qver < 401) && (systype != 1))
			p = (struct qs *) ((char *) p - 4);
		gf = ((p->core == 0xC9) || (p->core == 0xCA));
		if ((systype == 2) && (!gf))	/* There was never, in fact, any Mac Genome core */
		{	na = (p->wuid.f.run[0] & 0xFE) + (p->wuid.f.clone[0] & 0xFE)
					+ (p->wuid.f.gen[0] & 0xFE);
			nc = (p->wuid.f.run[1] & 0xFE) + (p->wuid.f.clone[1] & 0xFE)
					+ (p->wuid.f.gen[1] & 0xFE);
			if (na <= nc)	/* Test for already endian swapped (probably not needed) */
			{	exch(p->wuid.f.run[0], p->wuid.f.run[1], na);
				exch(p->wuid.f.clone[0], p->wuid.f.clone[1], na);
				exch(p->wuid.f.gen[0], p->wuid.f.gen[1], na);
			}
		}
		proj = gf ? le2(p->wuid.g.proj) : le2(p->wuid.f.proj);
		switch (estat)
		{
		case 0:
			if (proj == 0)
				q = "1empty";
			else if (p->ustat == 0)
				q = "0deleted";
			else if (p->ustat == 1)
				q = "0finished";
			else
				q = "1garbage";
			break;
		case 1:
			if (n == bp->current)
				q = "2folding now";
			else
				q = "3queued for processing";
			break;
		case 2:
			q = "0ready for upload";
			break;
		case 3:				/* Before version 3 beta 5 sometimes */
			if (n == bp->current)
				q = "2DANGER will be lost if client is restarted!";
			else
				q = "2abandoned";
			break;
		case 4:
			q = "1fetching from server";
			break;
		default:
			sprintf(wbuf, "0UNKNOWN STATUS = %d", estat);
			q = wbuf;
		}
		j = *q++ - '0';
		sprintf(pf, " index %u:", n);
		ps->id = QB_QD + n;
		ps->text = pf;
		pf += strlen(pf) + 1;
		(++ps)->flags = BF_B;
		ps->id = -1;
		ps->text = pf;
		if (proj != 0)
			sprintf(pf, "%4d %s", proj, q);
		else
			sprintf(pf, "     %s", q);
		pf += strlen(pf) + 1;
		(++ps)->flags = BF_C;
		if (n == qiselect - QB_QD)
			f = j;
	}
	ps->flags = BF_R;
	ps->id = -1;
	ps->text = "";

	/* Print more from selected entry */

	n = qiselect - QB_QD;
	p = &bp->entry[n];
	if (qver < 324)
		p = (struct qs *) ((char *) p - 16 * n);
	if (qver < 500)
		p = (struct qs *) ((char *) p - 8 * n);
	if ((qver < 401) && (systype != 1))
		p = (struct qs *) ((char *) p - 4 * n - 4);
	gf = ((p->core == 0xC9) || (p->core == 0xCA));
	tp = p->tdata;				/* Beginning and ending times */
	dp = p->due;				/* Due date */
	if ((systype == 2) || (qver <= 9))
	{	++tp;			/* Older versions and OS X are 4 bytes higher */
		++dp;
	}

	na = 0;
	nb = -1;
	nc = 0;
	fc = 0.0;
	fcl = 0.0;
	tcl = 0;
	tcc = 0;
	st.st_mtime = 0;
	for (i = 0; ++i <= 8; )
	{	(++ps)->flags = BF_R;
		ps->id = -1;
		ps->text = pf;			/* The string will be built here */
		*pf = '\0';
		switch (i)
		{
		case 1:			/* Line 1: Core_XX "project name" */
			if (f == 1)
			{	ps->text = "(no information at selected index)";
				i = 100;		/* Nothing else worth printing */
				break;
			}
			sprintf(pf, "Core_%02x", p->core);
			pf += strlen(pf);	/* Adjust for appending to string */
			sprintf(wbuf, "work/logfile_%02d.txt", n);
			strncpy(&filname[dnlen], wbuf, sizeof(filname) - 1 - dnlen);
			filname[sizeof(filname) - 1] = '\0';
			if	(	(stat(filname, &st) != 0)
				||	(st.st_size == 0)
				||	(st.st_mtime - TIME_OFS < tp[0])
				) st.st_mtime = 0;
			else if ((fp = fopen(filname,"rb")) != NULL)	/* Try to get project name */
			{	while (fgets(wbuf, sizeof(wbuf), fp) != NULL)
				{	if ((*pf == '\0') && (strncmp(wbuf, "Protein: ", 9) == 0))
					{	wbuf[strlen(wbuf) - 1] = '\0';	/* Get rid of the newline */
						sprintf(pf, " \"%.40s\"", &wbuf[9]);	/* Work unit name */
						if (f != 2)
							break;
					}
					if (sscanf(wbuf, "- Frames Completed: %d, Remaining: %d",
							&za, &nb) == 2)
					{	na = za;
						nb += na;		/* Tinker or Genome total frames */
						goto zn;
					}
					if (sscanf(wbuf, "[SP%*c] Designing protein sequence  %d of %d", &za, &nb) == 2)
					{	na = za - 1;
						goto zn;
					}
					if	(	(sscanf(wbuf, "[SP%*c]  %d.0 %c", &za, &zc) == 2)
						&&	(zc == '%')
						)
					{	nc = za;
						goto sl;
					}
					if	(	(sscanf(wbuf, "Iterations: %d of %d", &za, &nb) == 2)
						||	(sscanf(wbuf, "Completed %d out of %d", &za, &nb) == 2)
						||	(sscanf(wbuf, "Finished a frame (%d", &za) == 1)
						)
					{	na = za;
zn:						nc = 0;
sl:						fcl = fc;
						fc = (nb > 0) ? ((float) na + (float) nc / 100.0) / (float) nb : 0.0;
						tcl = tcc;
						tcc = 0;
						continue;
					}
					if	(	(strncmp(wbuf, "Timered checkpoint triggered", 28) == 0)
						||	(strncmp(wbuf, "Writing checkpoint files", 24) == 0)
						) ++tcc;
				}
				if (tcc > tcl) tcc = tcl;
				fclose(fp);
			}
			if	(	(*pf == '\0')
				&&	(n == bp->current)
				&&	((oflags & OF_XF) == 0)
				&&	(readxyz() == 0)
				) sprintf(pf, " \"%.40s\"", pname);		/* Current protein name */
			if ((f == 2) && (nb <= 0))		/* One last chance to get progress */
			{	sprintf(wbuf, "work/wuinfo_%02d.dat", n);
				strncpy(&filname[dnlen], wbuf, sizeof(filname) - 1 - dnlen);
				filname[sizeof(filname) - 1] = '\0';
				if (stat(filname, &st) != 0)
					st.st_mtime = 0;
				if ((fp = fopen(filname,"rb")) != NULL)
				{	if (fread(wbuf, 100, 1, fp) == 1)
					{	na = *((u16 *) &wbuf[88]);
						nb = *((u16 *) &wbuf[84]);
						nc = 0;
					}
					fclose(fp);
				}
			}
			break;

		case 2:			/* Line 2: proj XX, run XX, clone XX, gen XX */
			if (gf)			/* Genome */
				sprintf(pf, "genome proj %u", le2(p->wuid.g.proj));
			else					/* Folding */
				sprintf(pf, "proj %u run %u clone %u gen %u", le2(p->wuid.f.proj),
						le2(p->wuid.f.run), le2(p->wuid.f.clone), le2(p->wuid.f.gen));
			break;

		case 3:			/* Line 3: server XX.XX.XX.XX:XXXX */
			j = p->svr2;
			sprintf(pf, "server %u.%u.%u.%u",
					(j >> 24) & 0xFF, (j >> 16) & 0xFF, (j >> 8) & 0xFF, j & 0xFF);
			if (p->port != 0)
				sprintf(pf + strlen(pf), ":%u", p->port);
			break;

		case 4:			/* Line 4:   <user name>, team <team number> */
			strncpy(pf, p->uname, 40);
			pf[40] = '\0';
			if (p->teamn[0] != '\0')
				sprintf(pf + strlen(pf), ", team %.40s", p->teamn);
			break;

		case 5:			/* Line 5: begin <date> (XX%) */
			prtime(pf, " begin ", tp[0]);
			if (f == 2)
			{	fc = (nb > 0) ? ((float) na + (float) nc / 100.0) / (float) nb : 0.0;
				if ((tcl > 0) && (fcl > 0))
					fc += (fc - fcl) * (float) tcc / ((float) tcl + 0.5);
				if (fc > 0.0)			/* Did we get progress at all? */
					sprintf(pf + strlen(pf), "%9.3g%%", fc * 100.0);
			}
			break;

		case 6:			/* Line 6:   end <date> or expect <date> (XX x) */
			if ((f != 2) && (f != 3))
			{	prtime(pf, "   end ", tp[4]);
				j = tp[4];
				fc = 1.0;
			}
			else
			{	if (fc > 0.0)			/* Did we get progress at all? */
				{	if ((j = st.st_mtime) == 0)
						j = time(NULL);
					j -= TIME_OFS;
					prtime(pf, "expect ", tp[0] + (u32) ((float) (j - tp[0]) / fc));
				}
			}
			if	(	(fc > 0.0)
				&&	(j != 0)
				&&	((j -= tp[0]) > 0)
				&&	(qver >= 324)
				&&	(p->expire != 0)
				) sprintf(pf + strlen(pf), "%8.3g \327", (float) p->expire * fc / (float) j);
			break;

		case 7:			/* Line 7: due <date>  (xx dy) */
			if (qver < 324) break;
			if (p->svr1 == 0) break;
			if (dp[0] == 0) break;
			prtime(pf, "   due ", dp[0]);
			pf += strlen(pf);
			if (p->expire < 172800)		/* Two days */
				sprintf(pf, "%4d hours", (p->expire + 1800) / 3600);
			else
				sprintf(pf, "%5d days", (p->expire + 43200) / 86400);
			break;

		case 8:			/* Line 8: benchmark <number> */
			if (qver < 324) break;
			sprintf(pf, "benchmark %u", le4(p->bench));
			break;
		}
		pf += strlen(pf) + 1;	/* Adjust for end of string */
	}
rt:	(++ps)->flags = 0;
	if (debug >= 2)
		printf("qibuf use: %u bytes of %u\n", pf - qibuf, rankof(qibuf));
	iscreen(qiscreen);
	clrscreen();
}
