/*ScianHelp.c
  Eric Pepke
  3 September 1991
  On-line help system for SciAn
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianMethods.h"
#include "ScianLists.h"
#include "ScianIDs.h"
#include "ScianStyle.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianEvents.h"
#include "ScianDialogs.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianArrays.h"
#include "ScianTextBoxes.h"
#include "ScianErrors.h"
#include "ScianScripts.h"
#include "ScianColors.h"


/*Topics for help*/
#define TOPIC_INTRO		0
#define TOPIC_COPYRIGHT		1
#define TOPIC_CREDITS		2
#define TOPIC_BUGS		3
#define TOPIC_REVISION		4
#define TOPIC_COMMANDLINE	5
#define TOPIC_MOUSE		6
#define TOPIC_MODIFIERS		7
#define TOPIC_FKEYS		8
#define TOPIC_MENUS		9
#define TOPIC_CONTEXT		10
#define NTOPICS			11

#define ISVOWEL(c) ((c) == 'a' || (c) == 'A' || (c) == 'e' || (c) == 'E' \
		|| (c) == 'i' || (c) == 'I' || (c) == 'o' || (c) == 'O' \
		|| (c) == 'u' || (c) == 'U')

typedef struct
    {
	Bool indent;		/*True iff indent*/
	char *buttonName;	/*Name of the button*/
	char *topicName;	/*Name of the topic*/
	char *topicString;	/*String of the topic*/
    } HelpInfo;

HelpInfo helpInfo[NTOPICS] =
    {
	{false, "Introduction", "Intro", 
	 "Welcome to SciAn, a scientific visualization and animation package \
developed at the Supercomputer Computations Research Institute (SCRI) at Florida \
State University.\n\nThis on-line help system is intended to help you get started \
using SciAn.  To see help on a particular topic, click with the left mouse \
button on the radio button on the right showing the name of the topic.\n\
\n\
If there is more help text than can be shown on the screen, move up and \
down through the text using the scroll bar on the right."},
	{false, "Copyright", "Copy",
	 "SciAn is Copyright \201 1991-1993 by the Florida State University.  \
All rights reserved.\n\
\n\
You may distribute the source and binary of this program provided that\n\
1) All distributions retain this entire copyright notice\n\
2) All documentation and advertising mentioning features or the use of this \
software include the acknowlegement, \"Portions developed \
by the Supercomputer Computations Research Institute at Florida State University \
and its contributors.\"\n\
\n\
You may not use the name of the University, the Institute, or any of its contributors \
to endorse or promote products derived from this software without specific prior written permission.\n\
\n\
If you use any images produced by SciAn in papers, conference proceedings, etc. \
please acknowledge the Supercomputer Computations Research Institute \
and Florida State University."},
	{false, "Credits", "Cred",
#ifdef HDFDEF
	 "SciAn was written by Eric Pepke, John Murray and Jim Lyons.  \
Additions to SciAn were written by Doug Lee, Marvin Landis, and Tzong-Yow Hwu.  \
The SciAn logo and T-shirt were designed by Dave Poindexter.\n\
\n\
SciAn is dedicated to Mike Mermikides, long an advocate of interactive computer \
graphics and a pioneer in the use of socket communication.\n\
\n\
SciAn uses algorithms and techniques developed by Donna Cox, M. Chen, S. J. Mountford, \
A. Sellen, and Thant Tessman.\n\
\n\
Many people have contributed in many ways to the SciAn project.  Among them are \
Ken Johnson, Dennis Duke, Hwa Lim, Andy Hasenfeld, Saul Youssef, Jerry Magnan, \
P.K. Jayakumar, Sergiu Sanielevici, Gregor Bali, Dave Kopriva, Taekyu Reu, Susan X. \
Ying, Jim Carr, Mike Mermikides, Sam Adams, Dave Poindexter, Jim O'Brien, Mark Luther, \
John McCalpin, and Al Davis.\n\
\n\
Partially funded by the U.S. Department of Energy under contract DE-FC05-85ER250000.\n\
Supported by the State of Florida.\n\
\n\
Portions developed at the National Center for Supercomputing Applications at \
the University of Illinois at Urbana-Champaign."},
#else
	 "SciAn was written by Eric Pepke, John Murray, Jim Lyons, and Doug Lee.  \
The SciAn logo and T-shirt were designed by Dave Poindexter.\n\
\n\
SciAn is dedicated to Mike Mermikides, long advocate of interactive computer\n\
graphics and pioneer in the use of socket communication.\n\
\n\
Many people have contributed in many ways to the SciAn project.  Among them are \
Ken Johnson, Dennis Duke, Hwa Lim, Andy Hasenfeld, Saul Youssef, Jerry Magnan, \
P.K. Jayakumar, Sergiu Sanielevici, Gregor Bali, Dave Kopriva, Taekyu Reu, Susan X. \
Ying, Jim Carr, Mike Mermikides, Sam Adams, Dave Poindexter, Jim O'Brien, Mark Luther, \
John McCalpin, and Al Davis.\n\
\n\
Thanks to Marvin Landis for writing the Panasonic TQ2026F driver.\
\n\
Partially funded by the U.S. Department of Energy under contract DE-FC05-85ER250000.\n\
Supported by the State of Florida"},
#endif
	{false, "Revision History", "Revision",
	 "\
0.742\t\t3/1/92\t\t\tRelease version\n\
0.73\t\t2/11/92\t\t\tBeta release\n\
0.72\t\t12/18/92\t\t\tBeta release with some bug fixes\n\
0.71\t\t12/18/92\t\t\tRelease version\n\
0.70\t\t12/8/92\t\t\tImproved IBM compatibility, changed space controls, \
added PDB and P3D readers, added Ball and Point Cloud vis objects\n\
0.66\t\t9/29/92\t\t\tRelease version\n\
0.65\t\t9/29/92\t\t\tScript and color table bugs fixes\n\
0.64\t\t9/21/92\t\t\tIBM compatibility restored, flight simulator changed, internal mods to \
observer, script snapshots\n\
0.63\t\t9/17/92\t\t\tBeta release\n\
0.62\t\t9/16/92\t\t\tFixed bugs\n\
0.61\t\t9/15/92\t\t\tFixed bugs\n\
0.60\t\t9/10/92\t\t\tIRIS Indigo compatibility, STF reader\n\
0.59\t\t9/1/92\t\t\tImproved arrows, slices, meshes\n\
0.58\t\t8/17/92\t\t\tFixed some bugs, put back noborder\n\
0.57\t\t8/14/92\t\t\tFixed some bugs, sped up drawing\n\
0.56\t\t7/22/92\t\t\tFixed problem with frame advance while recording\n\
0.55\t\t6/26/92\t\t\tFixed problem with palettes read from disk\n\
0.54\t\t6/26/92\t\t\tMinor modifications to 0.53\n\
0.53\t\t6/19/92\t\t\tIBM drag, TQ2026F driver, horizontal palette displays\n\
0.52\t\t6/1/92\t\t\tFixed script problems, sped up isosurfaces\n\
0.51\t\t5/31/92\t\t\tIRIX 4, AIX 3.2, and better VGX compatibility, improved surface normals\n\
0.50\t\t5/18/92\t\t\tAdded contours, space panel tools\n\
0.42\t\t1/25/92\t\t\tFixed some script problems, problem with time-dependent files and min/max, \
removed file extensions from dataset names\n\
0.41\t\t1/5/92\t\t\tImproved color editor, fixed geometric object and NFF bugs\n\
0.40\t\t11/15/91\t\t\tFirst beta release\n\
"},
	{false, "Reporting Bugs", "Bugs",
	 "We appreciate your help in testing SciAn.  Unfortunately, you will \
most likely find some bugs in the program.\n\n\
If you find a bug, first check to see if it is listed in the list of known bugs (if you have been \
given such a list, that is).  Lists will appear occasionally on the SciAn mailing list.  \
If it is not on the list, try to replicate the bug while \
logging to a file from within SciAn (using the -l filename switch on the command line).  Some bugs will \
produce error messages to the standard error device, and these error messages will also \
appear in the log.  If the bug results in a core dump, keep the core file too.\n\n\
Please report the bug via electronic mail to scian-bugs@scri.fsu.edu.  You don't \
need to include the core file when you first report a bug, but we may need you to send it \
along later.\n\
\n\
If you are not on the network or do not have access to electronic mail, call us and we \
will find some other way for you to report bugs."},
	{false, "Command Line", "CmdLine",
	 "scian\t\t[-s script] [-l log] [-1c] [-1o] [-1l] [-1r] [-SCvd] [-o directory] [-f format] [files...]\n\
\n\
-C\t\t\tNormally, SciAn aborts on errors in scripts.  \
This switch causes SciAn to continue to read past script errors.\n\
\n\
-d\t\t\tRuns SciAn in demo mode, where the text is a little \
blacker to make it easier for onlookers to see.\n\
\n\
-f format\t\t\tSpecifies format as \
the file format for subsequent files read on the command line.  This switch overrides \
the normal guessing of format based on the file extension.\n\
\n\
files...\t\t\tReads the data files in order.  If no -f switch has appeared, \
the format will be guessed according to the file extension.\n\
\n\
-l log\t\t\tLogs everything which happens in a session with SciAn to a log file.  \
This log file can be read back into SciAn using the -s switch.\n\
\n\
-o directory\t\t\tOpens directory as a file directory window.\n\
\n\
-s script\t\t\tRuns the script in file script.  \
If present, this switch should be the first switch on the command line.\n\
\n\
-S\t\t\tNormally, scripts do not include comments to select objects.  This is \
most desirable for scripts used to make movies.  The -S switch causes logs to be \
produced including object selections.\n\
\n\
-v\t\t\tEnables video recording.  If it does not appear on the \
command line, video commands will just result in debugging messages echoed to the \
console.  It is best to run a script once without the -v switch to make \
sure it will work before doing the actual recording.\n\
\n\
-1c\t\t\tNormally, each space has its own clock.  This switch causes only one \
clock to be created for all spaces.\n\
\n\
-1l\t\t\tNormally, each space has its own set of lights.  This switch causes only one \
set of lights to be created for all spaces.\n\
\n\
-1o\t\t\tNormally, each space has its own observer.  This switch causes only one \
observer to be created for all spaces.\n\
\n\
-1p\t\t\tNormally, each dataset has its own color palette.  This switch causes only one \
palette to be created for all datasets.\n\
\n\
-1r\t\t\tNormally, each space has its own renderer.  This switch causes only one \
renderer to be created for all spaces."
	},
	{false, "Using the Mouse", "Mouse",
	 "Most of the interaction with SciAn uses the three-button mouse \
to manipulate objects such as icons and controls.  Some actions are performed through menus.\n\
\n\
The left mouse button is used to select and drag objects, press and slide controls, select text, and select tools.  \
Double-clicking \
on an object such as an icon will usually perform the most common operation on that object.  \
See Help In Context to determine the double-click action for any given object.\n\
\n\
In a space, when the Move/Rotate Space tool is selected, the center \
mouse button is used to rotate the space.  Elsewhere, it acts the same as \
the left mouse button with the Shift key held down.\n\
\n\
The right mouse button is used to bring up menus.  Move the mouse anywhere within a SciAn window \
and press and hold the right button."
	},
	{false, "Modifier Keys", "Keys",
	 "Some keys on the keyboard modify the meaning of mouse actions if you \
hold them down while performing the action.\n\
\n\
The Shift key usually constrains motion in convenient ways.  Try it when dragging \
on spaces, controls, and text box handles.  When used on icons in an icon corral, the shift \
key allows a selection to be extended.\n\
\n\
The Alt key or Control key provides an optional way of doing some actions.  For \
example, if you hold down the Alt key when you click on the Visualize button, \
the visualizations will come up turned off instead of turned on.  The Alt key will \
also prevent redraws in other windows until you release it.\n\
\n\
See Help In Context to determine what each modifier key will do with a specific \
control.\n\
\n\
WARNING!  The modifier keys are set up so that they work fine on the default configurations \
of the Silicon Graphics and IBM window managers.  If you have any settings in your \
window manager configuration that intercepts any of these keys or mouse buttons, \
this may cause them not to work in SciAn."},
	{false, "Function Keys", "FKeys",
"F2\t\tCut Text\n\
\t\tThis key cuts the selected text from an editable text box into the SciAn clipboard.  \
The SciAn clipboard only works within SciAn.\n\
\n\
F3\t\tCopy Text\n\
\t\tThis key copies the selected text from an editable text box into the SciAn clipboard.\n\
\n\
F4\t\tPaste Text\n\
\t\tThis key pastes the text most recently cut or copied into the currently \
selected range of text.\n\
\n\
F5\t\tShow Controls\n\
\t\tThis key shows the controls for the selected objects.  It has the same effect \
as the Show Controls item in the Object menu.\n\
\n\
F7\t\tPush to Bottom\n\
\t\tThis key moves the selected objects in a space panel below the other objects.  \
This key has the same effect as the Push to Bottom item in the Arrange submenu of the Object menu.\n\
\n\
F8\t\tBring to Top\n\
\t\tThis key moves the selected objects in a space panel above the other objects.  \
This key has the same effect as the Bring to Top item in the Arrange submenu of the Object menu.\n\
\n\
F9\t\tPick Up\n\
\t\tThis key picks up the selected objects.  It has the same effect as the Pick Up item \
in the Object menu.\n\
\n\
F10\t\tSelect All\n\
\t\tThis key selects all icons in the window.\n\
\n\
F11\t\tDeselect All\n\
\t\tThis key deselects all selected objects.\n\
\n\
F12\t\tContext Sensitive Help\n\
\t\tThis key does the same thing as pressing the Help In Context button on this window.\n\
\n\
Print Scrn\n\t\tThis key saves the window pointed to by the cursor as an IRIS RGB \
file.  It only works on Silicon Graphics IRIS systems.  If the Alt key is held down, \
the entire screen will be saved.  If the Shift key is held down, only the contents of the \
window will be saved, not the window frame.  The cursor will not appear in \
the saved image."
},
	{false, "Menus", "Menu",
	 "Pressing and holding the right mouse button within a window \
brings up the main menu for that window, which contains individual items and submenus.  The main \
menu and its submenus are specific to the window and may vary depending on which objects \
within the window are selected.\n\
\n\
The Help item shows this help window.  It is always active.\n\
\n\
The Preferences item shows the preferences dialog window, which allows you \
to customize the operation of SciAn.\n\
\n\
The File submenu contains actions for working with files and file readers.\n\
\n\
The Datasets item shows the Datasets window.  Normally, the Datasets window is \
shown automatically when a new dataset is read.  This item provides an easy way \
to show the window if it is closed or hidden.\n\
\n\
The Object submenu contains actions that can be performed on all selected objects.\n\
\n\
The Text submenu contains actions to edit text and change the font and size.\n\
\n\
The Window submenu contains actions that can be performed on a window, such as resizing \
and tiling.\n\
\n\
The Quit SciAn item causes SciAn to quit."
	},
	{false, "Help In Context", "CSH",
#ifdef CURSORS4D
"Notice that your mouse pointer has changed to look like a question mark.  \
Click now within any control, icon, or window to find out information \
about it.  If you decide that you don't really want information about any \
particular control, you can click on this text box to return your mouse pointer \
to normal."
#else
"Click now within any control, icon, or window to find out information \
about it.  If you decide that you don't really want information about any \
particular control, you can click on this text box to return your mouse pointer \
to normal."
#endif
}
    };

static ObjPtr ChangeHelpRadio(radio)
ObjPtr radio;
/*ChangeValue as a result of a click in the help radio*/
{
    ObjPtr repObj;
    ObjPtr value;
    ObjPtr scrollbar;
    ObjPtr contents;
    int k;

    repObj = GetObjectVar("ChangeHelpRadio", radio, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(radio);
    if (!value)
    {
	return ObjFalse;
    }

    k = GetInt(value);

    if (k == TOPIC_CONTEXT)
    {
#ifdef INTERACTIVE
	SetContextSensitiveHelp();
#endif
    }

    contents = GetVar(radio, HELPCONTENTS);
    if (!contents)
    {
	return ObjFalse;
    }

    if (k >= 0 || k < DIMS(contents)[0])
    {
	/*There's a predefined string*/
	ObjPtr *elements;
	elements = ELEMENTS(contents);
	SetVar(repObj, CONTENTS, elements[k]);
    }
    
    scrollbar = GetVar(repObj, VSCROLL);
    if (scrollbar)
    {
	SetSliderValue(scrollbar, 0.0);
	RecalcScroll(repObj);
    }
    ImInvalid(repObj);
    return ObjTrue;
}

void DoShowHelp()
/*Shows the help window*/
{
    WinInfoPtr dialogExists, newDialog;
    ObjPtr whichDialog;

    Log("show help\n");
    InhibitLogging(true);

    whichDialog = NewString("Help");

    dialogExists = DialogExists(0, whichDialog);
    newDialog = GetDialog(0, whichDialog, "Help",
	HWWIDTH,
	HWHEIGHT,
	HWWIDTH,
	HWHEIGHT,
	WINDBUF + WINFIXEDSIZE);

    if (!dialogExists)
    {
	/*Fill the dialog with stuff*/
	ObjPtr contents, panel, field, button, radio, textBox, helpContents;
	int titleHeight;
	ObjPtr *elements;
	int k;
	long dim;
	int left, right, bottom, top;

	SetVar((ObjPtr) newDialog, HELPSTRING,
	    NewString("This window shows the on-line help for SciAn.  For more \
information on a topic, click on its name."));

	contents = GetListVar("DoShowHelp", (ObjPtr) newDialog, CONTENTS);
	if (!contents) return;
	panel = NewPanel(greyPanelClass, 
	    0, HWWIDTH, 
	    0, HWHEIGHT);
	if (!panel)
	{
	    return;
	}
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) newDialog);
	ContentsExpectWindowSize(newDialog, HWWIDTH, HWHEIGHT);

	contents = GetListVar("DoShowHelp", panel, CONTENTS);
	if (!contents) return;

	/*Make all the radio buttons*/
	radio = NewRadioButtonGroup("Topics");
	SetVar(radio, HELPSTRING,
	    NewString("These radio buttons show the names of topics on which you can get help.")); 

	dim = NTOPICS;
	helpContents = NewArray(AT_OBJECT, 1, &dim);
	if (!helpContents)
	{
	    return;
	}
	elements = ELEMENTS(helpContents);
	SetVar(radio, HELPCONTENTS, helpContents);

	field = NewControlField(MINORBORDER, HWWIDTH - 2 * MAJORBORDER - HWRADIOWIDTH,
			 MINORBORDER, HWHEIGHT - MINORBORDER,
			 "Help text", OBJECTSFROMTOP | BARRIGHT);	
	if (!field)
	{
	    return;
	}
	PrefixList(contents, field);
	SetVar(field, PARENT, panel);
	SetVar(field, BACKGROUND, NewInt(UIGRAY75));
	SetVar(field, BORDERTYPE, NewInt(1));

	top = HWHEIGHT - MAJORBORDER;
	for (k = 0; k < NTOPICS; ++k)
	{
	    elements[k] = NewList();
	    if (!elements[k])
	    {
		return;
	    }
	    switch(k)
	    {
		default:
		    /*Do a title text box*/
		    textBox = NewTextBox(MINORBORDER, HWWIDTH - MINORBORDER - 3 * MAJORBORDER - HWRADIOWIDTH - BARWIDTH,
					 -MINORBORDER - HWWIDTH,
					 -MINORBORDER,
					 0, "Title", helpInfo[k] . buttonName);
		    SetVar(textBox, TEXTFONT, NewString(HELPFONT));
		    SetVar(textBox, TEXTSIZE, NewInt(HELPTITLESIZE));
		    SetVar(textBox, PARENT, field);
		    titleHeight = TextHeight(textBox);
		    Set2DIntBounds(textBox,
			MINORBORDER, HWWIDTH - MINORBORDER - 3 * MAJORBORDER - HWRADIOWIDTH - BARWIDTH,
			-MINORBORDER - titleHeight,
			-MINORBORDER);
		    PrefixList(elements[k], textBox);
		
		    textBox = NewTextBox(MINORBORDER, HWWIDTH - MINORBORDER - 3 * MAJORBORDER - HWRADIOWIDTH - BARWIDTH,
					 -MINORBORDER - HWWIDTH - titleHeight,
					 -MINORBORDER - titleHeight,
					 0, helpInfo[k] . topicName, helpInfo[k] . topicString);
		    SetVar(textBox, TEXTFONT, NewString(HELPFONT));
		    SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
		    SetVar(textBox, PARENT, field);
		    Set2DIntBounds(textBox,
			MINORBORDER, HWWIDTH - MINORBORDER - 3 * MAJORBORDER - HWRADIOWIDTH - BARWIDTH,
			-MINORBORDER - titleHeight - TextHeight(textBox),
			-MINORBORDER - titleHeight);
		    PrefixList(elements[k], textBox);
	    }
	    button = NewRadioButton(HWWIDTH - MAJORBORDER - HWRADIOWIDTH,
				    HWWIDTH - MAJORBORDER,
				    top - CHECKBOXHEIGHT,
				    top,
				    helpInfo[k] . buttonName);
	    AddRadioButton(radio, button);
	    top -= CHECKBOXHEIGHT + CHECKBOXSPACING;
	}

	SetVar(radio, PARENT, panel);
	SetMethod(radio, CHANGEDVALUE, ChangeHelpRadio);
	PrefixList(contents, radio);


	SetVar(radio, REPOBJ, field);
	SetVar(radio, HALTHELP, ObjTrue);

	/*Finally, set the radio button*/
	SetValue(radio, NewInt(0));
	SetVar((ObjPtr) newDialog, HELPRADIO, radio);
    }
    InhibitLogging(false);
}

static ObjPtr helpObject;

void DoContextHelp()
/*Gives context sensitive help for screen object helpObject*/
{
    WinInfoPtr theDialog;
    ObjPtr whichDialog;

    DoShowHelp();
    whichDialog = NewString("Help");

    theDialog = DialogExists(0, whichDialog);
    if (theDialog)
    {
	FuncTyp method;
	ObjPtr radio, repObj, scrollbar;
	ObjPtr textBox, contents;
	int top, left, right, bottom;
	char *oldType = 0;
	char objectString[400];
	char *s;

	radio = GetObjectVar("ContextHelp", (ObjPtr) theDialog, HELPRADIO);
	if (!radio) return;

	method = GetMethod(radio, CHANGEDVALUE);
	SetMethod(radio, CHANGEDVALUE, (FuncTyp) 0);
	SetValue(radio, NewInt(TOPIC_CONTEXT));
	SetMethod(radio, CHANGEDVALUE, method);

	repObj = GetObjectVar("ContextHelp", radio, REPOBJ);
	if (!repObj) return;

 	scrollbar = GetVar(repObj, VSCROLL);
	if (scrollbar)
	{
	    SetSliderValue(scrollbar, 0.0);
	    RecalcScroll(repObj);
	}

	top = -MINORBORDER;
	left = MINORBORDER;
	right = HWWIDTH - MINORBORDER - 3 * MAJORBORDER - HWRADIOWIDTH - BARWIDTH;

	contents = NewList();

	/*Now build up the helpObject*/
	if (helpObject == repObj)
	{
	    /*It's a click in the text box*/
	    s = "Your mouse pointer has been returned to normal.  If you want to get \
help in context for any other object, click on \"Help In Context\" once more.";
	    bottom = top -
		(HELPSIZE + DEFLINESPACE) * (1 + LineCount(s, HWWIDTH - 2 * MINORBORDER - 3 * MAJORBORDER
			- HWRADIOWIDTH - BARWIDTH));

	    textBox = NewTextBox(left, right, bottom, top, 0, "info", s);
	    SetVar(textBox, TEXTFONT, NewString(HELPFONT));
	    SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
	    PrefixList(contents, textBox);
	}
	else while (helpObject)
	{
	    ObjPtr var;
	    ObjPtr class;
	    Bool firstTime = true;

	    /*Get help for all the classes*/
	    class = helpObject;
	    while (class)
	    {
		FuncTyp method;
		method = Get1Method(class, MAKE1HELPSTRING);
		if (method)
		{
		    (*method)(helpObject, class);
		}
		var = Get1Var(class, HELPSTRING);
		if (var)
		{
		    if (firstTime)
		    {
			ObjPtr var;
			char *name, *type;

			/*Print out the introduction*/
			firstTime = false;

			/*Get the type and name strings*/
			var = GetVar(helpObject, NAME);
			if (var)
			{
			    name = GetString(var);
			}
			else
			{
			    name = 0;
			}
			
			var = GetVar(helpObject, TYPESTRING);
			if (var)
			{
			    type = GetString(var);
			}
			else
			{
			    type = 0;
			}
			if (name || type)
			{
			    /*Print out help for this object*/
			    if (!type)
			    {
				type = "object";
			    }
			    if (name && (0 == strcmp2(type, name)))
			    {
				name = 0;
			    }

			    /*Generate the object string*/
			    s = objectString;
			    if (oldType)
			    {
				sprintf(s,
				    "The %s is located within a%s %s",
				    oldType,
				    ISVOWEL(*type) ? "n" : "",
				    type);
				while (*s) ++s;
				if (name)
				{
				    sprintf(s, " named \"%s.\"", name);
				}
				else
				{
				    sprintf(s, ".");
				}
				bottom = top - 37;

				textBox = NewTextBox(left, right, bottom, top, 0, "info", objectString);
				SetVar(textBox, TEXTFONT, NewString(HELPFONT));
				SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
				bottom = top - TextHeight(textBox);
				Set2DIntBounds(textBox, left, right, bottom, top);
				PrefixList(contents, textBox);
			    }
			    else
			    {
				sprintf(s,
				    "Help for a%s %s",
				    ISVOWEL(*type) ? "n" : "",
				    type);
				while (*s) ++s;
				    if (name)
				{
				    sprintf(s, " named \"%s.\"", name);
				}
				else
				{
				    sprintf(s, ".");
				}

				textBox = NewTextBox(left, right, bottom, top, 0, "info", objectString);
				SetVar(textBox, TEXTFONT, NewString(HELPFONT));
				SetVar(textBox, TEXTSIZE, NewInt(HELPTITLESIZE));
				bottom = top - TextHeight(textBox);
				Set2DIntBounds(textBox, left, right, bottom, top);
				PrefixList(contents, textBox);
			    }

			    oldType = type;
			    top = bottom;
			}

		    }

		    s = GetString(var);
		    bottom = top - 37;

		    textBox = NewTextBox(left, right, bottom, top, 0, "info", s);
		    SetVar(textBox, TEXTFONT, NewString(HELPFONT));
		    SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
		    bottom = top - TextHeight(textBox);
		    Set2DIntBounds(textBox, left, right, bottom, top);
		    PrefixList(contents, textBox);

		    top = bottom;

		}
		class = ClassOf(class);
	    }

	    if (GetPredicate(helpObject, HALTHELP))
	    {
		helpObject = NULLOBJ;
	    }
	    else
	    {
		helpObject = GetVar(helpObject, PARENT);
	    }
	}

	SetVar(repObj, CONTENTS, contents);
	RecalcScroll(repObj);
	ImInvalid(repObj);
    }
}

static char *ObjTypeStr(object)
ObjPtr object;
/*Returns a temporary type string from flags*/
{
    if (!object)
    {
	return "NULLOBJ";
    }
    switch(OBJTYPE(object -> flags))
    {
    	case PLAINOBJECT:
	    return "Plain object";
	    break;
	case REALARRAY:
	    return "Real array";
	    break;
	case STRING:
	    return "String";
	    break;
	case INTEGER:
	    return "Integer";
	    break;
	case REAL:
	    return "Real number";
	    break;
	case LIST:
	    return "List";
	    break;
	case PALETTE:
	    return "Palette";
	    break;
	case MATRIX:
	    return "Matrix";
	    break;
	case WINDOW:
	    return "Window";
	    break;
	case PICTURE:
	    return "Picture";
	    break;
	case OBJECTARRAY:
	    return "Object array";
	    break;
	case CONNECTION:
	    return "Socket connection";
	    break;
    }
    return "Unknown";
}

static char *ObjValueStr(object)
ObjPtr object;
/*Returns a temporary value string from flags*/
{
    if (!object)
    {
	return "";
    }
    switch(OBJTYPE(object -> flags))
    {
	case STRING:
	    if (strlen(GetString(object)) < 20)
	    {
		sprintf(tempStr, "\"%s\"", GetString(object));
	    }
	    else
	    {
		tempStr[0] = '"';
		strncpy(&(tempStr[1]), GetString(object), 20);
		strcpy(&(tempStr[50]), "\"...");
	    }
	    return tempStr;
	    break;
	case INTEGER:
	    sprintf(tempStr, "%d", GetInt(object));
	    return tempStr;
	    break;
	case REAL:
	    sprintf(tempStr, "%g", GetReal(object));
	    return tempStr;
	    break;
	default:
#if 0
	    if (IsNetworkStub(object))
	    {
		return "NETWORK STUB";
	    }
#endif
	    sprintf(tempStr, "%lx", object);
	    return tempStr;
	    break;
    }
}

ObjPtr objectToSniff = 0;
void SniffObject(void);

static ObjPtr SniffNetRepobj(button)
ObjPtr button;
/* Sniffs a button's NetworkStub repobj */
{
    ObjPtr repObj, netRepObj;
    repObj = GetVar (button, REPOBJ);
    if (repObj)
    {
	ObjPtr parent;
	int val;

	parent = GetVar(repObj, PARENT);
	if (!parent)
	   return NULLOBJ;

	val = GetInt(GetIntVar("SniffNetRepobj", repObj, VALUE));
	/* may block waiting for network traffic */
	netRepObj = GetVar(parent, val);
	if (netRepObj)
	{
	    objectToSniff = netRepObj;
	    DoUniqueTask(SniffObject);
	}
    }
    return ObjTrue;
}

static ObjPtr SniffRepobj(button)
ObjPtr button;
/*Sniffs a button's repobj*/
{
    ObjPtr repObj;
    repObj = GetVar(button, REPOBJ);
    if (repObj)
    {
	objectToSniff = repObj;
	DoUniqueTask(SniffObject);
    }
    return ObjTrue;
}

static int StuffVarControls(vars, contents, top)
VarsPtr vars;
ObjPtr contents;
int top;
/*Stuffs controls for var in contents from top, returns new top*/
/* JRM 7-14-92 modified to account for remote objects and network stubs */
{
    ObjPtr object;
    ObjPtr textBox, button;
    int mid, bottom, left, right;
    int netstub;
    if (!vars) return top;

    top = StuffVarControls(vars -> left, contents, top);

    bottom = top - BUTTONHEIGHT;
    mid = (bottom + top) / 2;

    /*Create the ID text box*/
    left = MINORBORDER;
    right = left + SNIFFIDWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", IDName(vars -> name));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    netstub = NETSTUBFLAG == vars -> remoteNetID;

    if (!netstub)
    {
	object = vars -> value;
    }
    else
    {
	object = NewObject(NULLOBJ, 0);
	SetVar(object, PARENT, objectToSniff);
	SetVar(object, VALUE, NewInt(vars -> name));
    }

    /*Create the type box*/
    right = left + SNIFFTYPEWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", netstub ? "NetStub" : ObjTypeStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    /*Create the value box*/
    right = left + SNIFFVALWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", netstub ? "NetStub" : ObjValueStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    SetVar(textBox, REPOBJ, object);
    SetMethod(textBox, DOUBLECLICK, netstub ? SniffNetRepobj : SniffRepobj);
    left = right + MINORBORDER;


    /*Create the show button box*/
    if (object)
    {
	right = left + SNIFFSHOWWIDTH;
	if (netstub)
	{
	button = NewButton(left, right,
			bottom, top, "Fetch");
	}
	else
	{
	button = NewButton(left, right,
			bottom, top, "Show");
	}
	SetVar(button, REPOBJ, object);
	SetVar(button, PARENT, contents);
	PrefixList(contents, button);
	SetMethod(button, CHANGEDVALUE, netstub ? SniffNetRepobj : SniffRepobj);
	left = right + MINORBORDER;
    }

    top = bottom - MINORBORDER;

    return StuffVarControls(vars -> right, contents, top);
}

#define HOWMANY 20

static int StuffListElements(sniffObj, contents, top)
ObjPtr sniffObj;
ObjPtr contents;
int top;
/*Stuffs first HOWMANY list elements in contents from top, returns new top*/
{
    ObjPtr object;
    ObjPtr textBox, button;
    int mid, bottom, left, right;
    ThingListPtr runner;
    int howMany;
    char str[100];

    runner = LISTOF(sniffObj);
    howMany = 0;

    while (runner && howMany++ < HOWMANY)
    {
	

    bottom = top - BUTTONHEIGHT;
    mid = (bottom + top) / 2;

    /*Create the ID text box*/
    sprintf(str, "List Element #%d", howMany);
    left = MINORBORDER;
    right = left + SNIFFIDWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", str);
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    object = runner -> thing;

    /*Create the type box*/
    right = left + SNIFFTYPEWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", ObjTypeStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    /*Create the value box*/
    right = left + SNIFFVALWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", ObjValueStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    SetVar(textBox, REPOBJ, object);
    SetMethod(textBox, DOUBLECLICK, SniffRepobj);
    left = right + MINORBORDER;

    /*Create the show button box*/
    if (object)
    {
	right = left + SNIFFSHOWWIDTH;
	button = NewButton(left, right,
			bottom, top, "Show");
	SetVar(button, REPOBJ, object);
	SetVar(button, PARENT, contents);
	PrefixList(contents, button);
	SetMethod(button, CHANGEDVALUE, SniffRepobj);
	left = right + MINORBORDER;
    }

    top = bottom - MINORBORDER;

    runner = runner -> next;

    }

    return top;
}

static real *elPtr;

static ObjPtr EmitRealArrayElements(s, array, rank)
ObjPtr s;
ObjPtr array;
int rank;
/*Emit series of real array elements of array at rank rank into s.  Uses global
elPtr to run through the elements*/
{
    ObjPtr retVal = s;
    if (rank >= RANK(array) - 1)
    {
	/*Spit out the elements*/
	long k;
	for (k = 0; k < DIMS(array)[rank]; ++k)
	{
	    sprintf(tempStr, "%g", *elPtr++);
	    if (k < DIMS(array)[rank] - 1)
	    {
		strcat(tempStr, " ");
	    }
	    retVal = ConcatStrings(retVal, NewString(tempStr)); 
	}
    }
    else
    {
	/*Go down recursively*/
	retVal = ConcatStrings(retVal, NewString("["));
	retVal = ConcatStrings(EmitRealArrayElements(retVal, array, rank + 1),
				NewString("]"));
    }
    return retVal;
}

void SniffObject()
/*Sniffs objectToSniff*/
{
    WinInfoPtr objWin;
    ObjPtr panel, contents, field, textBox, button;
    int left, right, bottom, top, mid;
    char title[256];
    ObjPtr object;

    if (!objectToSniff)
    {
	return;
    }

    strcpy(title, ObjTypeStr(objectToSniff));
    strcat(title, " ");
    strcat(title, ObjValueStr(objectToSniff));

    objWin = NewObjWindow(NULLOBJ, title, WINDBUF + WINFIXEDSIZE,
		SNIFFWINWIDTH, SNIFFWINHEIGHT, SNIFFWINWIDTH, SNIFFWINHEIGHT);
    contents = GetVar((ObjPtr) objWin, CONTENTS);

    /*Create a panel*/
    panel = NewPanel(greyPanelClass, 0, SNIFFWINWIDTH, 0, SNIFFWINHEIGHT);
    SetVar(panel, PARENT, (ObjPtr) objWin);
    PrefixList(contents, panel);
    ContentsExpectWindowSize(objWin, SNIFFWINWIDTH, SNIFFWINHEIGHT);

    contents = GetListVar("SniffObject", panel, CONTENTS);
    if (!contents) return;

    field = NewControlField(MINORBORDER, SNIFFWINWIDTH - MINORBORDER,
			 MINORBORDER, SNIFFWINHEIGHT - MINORBORDER,
			 "Sniff box", OBJECTSFROMTOP | BARRIGHT);	
    if (!field)
    {
	return;
    }
    PrefixList(contents, field);
    SetVar(field, PARENT, panel);
    SetVar(field, BORDERTYPE, NewInt(1));
    SetVar(field, BACKGROUND, NewInt(UIGRAY62));

    contents = GetListVar("SniffObject", field, CONTENTS);
    if (!contents)
    {
	return;
    }
    SetVar(contents, PARENT, field);

    top = -MINORBORDER;

    /*Put in an entry for the class*/

    /*If it's a real array, emit the dang thing*/
    if (IsRealArray(objectToSniff))
    {
	ObjPtr s, textBox;
	
	s = NewString("[");
	elPtr = ELEMENTS(objectToSniff);
	s = EmitRealArrayElements(s, objectToSniff, 0);
	s = ConcatStrings(s, NewString("]"));

	left = MINORBORDER;
	right = left + SNIFFIDWIDTH + SNIFFTYPEWIDTH + SNIFFVALWIDTH + SNIFFSHOWWIDTH + MINORBORDER * 3;
	textBox = NewTextBox(left, right, bottom, top, 0, "value", GetString(s));
	SetVar(textBox, PARENT, contents);
	PrefixList(contents, textBox);
	SetVar(textBox, TEXTFONT, NewString(HELPFONT));
	SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
	bottom = top - TextHeight(textBox);
	Set2DIntBounds(textBox, left, right, bottom, top);
	top = bottom - MINORBORDER;
    }
    else if (IsString(objectToSniff))
    {
	ObjPtr textBox;
	
	left = MINORBORDER;
	right = left + SNIFFIDWIDTH + SNIFFTYPEWIDTH + SNIFFVALWIDTH + SNIFFSHOWWIDTH + MINORBORDER * 3;
	textBox = NewTextBox(left, right, bottom, top, 0, "value", GetString(objectToSniff));
	SetVar(textBox, PARENT, contents);
	PrefixList(contents, textBox);
	SetVar(textBox, TEXTFONT, NewString(HELPFONT));
	SetVar(textBox, TEXTSIZE, NewInt(HELPSIZE));
	bottom = top - TextHeight(textBox);
	Set2DIntBounds(textBox, left, right, bottom, top);
	top = bottom - MINORBORDER;
    }

    bottom = top - BUTTONHEIGHT;
    mid = (bottom + top) / 2;

    /*Create the class text box*/
    left = MINORBORDER;
    right = left + SNIFFIDWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", "Superclass");
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    object = ClassOf(objectToSniff);

    /*Create the type box*/
    right = left + SNIFFTYPEWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", ObjTypeStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    left = right + MINORBORDER;

    /*Create the value box*/
    right = left + SNIFFVALWIDTH;
    textBox = NewTextBox(left, right,
			  mid - TEXTBOXHEIGHT / 2, mid + TEXTBOXHEIGHT / 2,
			  0, "ha!", ObjValueStr(object));
    SetVar(textBox, PARENT, contents);
    PrefixList(contents, textBox);
    SetVar(textBox, TEXTFONT, NewString("Helvetica-Bold"));
    SetVar(textBox, REPOBJ, object);
    SetMethod(textBox, DOUBLECLICK, SniffRepobj);
    left = right + MINORBORDER;

    /*Create the show button box*/
    if (object)
    {
	right = left + SNIFFSHOWWIDTH;
	button = NewButton(left, right,
			bottom, top, "Show");
	SetVar(button, REPOBJ, object);
	SetVar(button, PARENT, contents);
	PrefixList(contents, button);
	SetMethod(button, CHANGEDVALUE, SniffRepobj);
	left = right + MINORBORDER;
    }

    top = bottom - MINORBORDER;

    if (IsList(objectToSniff))
    {
	top = StuffListElements(objectToSniff, contents, top);
    }

    /*Go through the variables*/
    top = StuffVarControls(objectToSniff -> vars, contents, top);

    RecalcScroll(field);
}

void ContextHelp(object)
ObjPtr object;
/*Does in-context help for object*/
{
    helpObject = object;
#ifdef GODLIKE
    if (AltDown() || OptionDown())
    {
	objectToSniff = object;
	DoUniqueTask(SniffObject);
    }
    else
#endif
    DoUniqueTask(DoContextHelp);
}

