/* Copyright(c) 1993 Association of Universities for Research in Astronomy Inc.
 */

#define Obm_Main
#include "ObmP.h"

/*
 * OBM.C -- Object manager for the graphics subsystem.
 *
 *               obm = ObmOpen (app_context, argc, argv)
 *                    ObmClose (obm)
 *               ObmInitialize (obm)
 *                 ObmActivate (obm)
 *               ObmDeactivate (obm, unmap)
 *       status = ObmActivated (obm)
 *	    status = ObmStatus (obm, app_name, app_class)
 *      status = ObmDeliverMsg (obm, object, message)
 *  st = ObmDeliverMsgFromFile (obm, object, fname)
 *	 interp = ObmGetInterp (obm)
 *
 * Callbacks:
 *
 *	   id = ObmAddCallback (obm, callback_type, fcn, client_data)
 *           ObmRemoveCallback (obm, id)
 *
 *	      connect_callback (client_data, display, toplevel, state)
 *	     activate_callback (client_data, toplevel)
 *	   deactivate_callback (client_data, toplevel)
 *	     setGterm_callback (client_data, gterm_widget)
 *	 clientOutput_callback (client_data, tcl, key, string)
 *
 * The callback type bitflags are defined in <Obm.h> as OBMCB_connect,
 * OBMCB_activate and so on.
 *
 * An xgterm graphics user interface (UI) consists of one or more windows
 * containing an arbitrary hierarchy of widgets.  These widgets and their
 * runtime actions are defined by an interpreted text program downloaded
 * by the client application, which does not itself deal directly with
 * the window user interface.
 *
 * The object manager provides a higher level of abstraction for dealing
 * with widgets and other UI objects.  The main function of the object
 * manager is to deliver messages to UI objects.  Each instance of a widget
 * is an object in the UI.  The UI contains other types of objects however,
 * including the client object (client application), the server object
 * (the object manager itself), and the application specific UI parameters,
 * each of which is an object with a callback list of UI procedures to be
 * called when the parameter value changes.  All of these UI objects can
 * receive messages and take actions as a result.  Messages may come from the
 * client application, or as a result of actions executed by the interpreted
 * UI code in response to graphics events.
 *
 * Object classes:
 *
 *	Client				the client application
 *	Server				the object manager itself
 *	Parameter			UI control parameter
 *	Gterm widget			graphics terminal widget
 *	Vterm widget			vt100 terminal widget
 *	various Xt and Athena widgets	box, shell, label, text, list, etc.
 *
 * In addition to delivering messages to objects (ObmDeliverMsg), one of the
 * functions of the object manager is to manage the hierarchy of widgets
 * comprising the UI.  ObmOpen opens the object manager.  ObmInitialize
 * initializes the object manager, destroying any existing widget hierarchy.
 * ObmActivate readies the UI to receive graphics output and graphics input
 * events, creating a default UI if none has been downloaded by the client.
 *
 * Sophisticated graphics applications will download a UI during initialization
 * to define a custom graphics user interface.  This is done by sending a
 * message to the object manager.  Naive applications assume a simple graphics
 * terminal and do not download a UI; in this case, a default UI is created
 * for the application when the UI is enabled with ObmEnable.  The default
 * UI is a single top level shell window containing a single gterm (graphics
 * terminal) widget.
 *
 *	reset-server
 *	appInitialize appname classname {resources}
 *	createObjects
 *		(UI specific code)
 *	activate
 *
 * A UI specification consists of a sequence of commands to be executed by
 * the server object.  This is downloaded by the client as a message for the
 * server object.  The commands should include "reset-server" (this must be
 * the first executable command), "appInitialize" (defines the UI objects and
 * their resources), and "createObjects" (creates the objects and the widget
 * tree), followed by any UI specific commands to define and register UI
 * callback procedures.  Finally, "activate" is executed to activate the new
 * user interface.
 */

#define	MAXOBJ	512
static void obm_call_activate_callbacks(struct obmContext *obm, int state);


#if (TCL_MAJOR_VERSION < 8)
#error Tcl 8 or higher is required
#endif

/* Backward compatibility patch for errorLine access. These functions were
 * introduced only in Tcl 8.6, and direct access to errorLine is deprecated
 * since then.
 */
#if (TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION < 6)
int
Tcl_GetErrorLine(Tcl_Interp *tcl)
{
	return tcl->errorLine;
}

void
Tcl_SetErrorLine(Tcl_Interp *tcl, int lineNum)
{
	tcl->errorLine = lineNum;
}
#endif


/* ObmOpen -- Open the object manager.
 */
ObmContext
ObmOpen (XtAppContext app_context, int argc, char *argv[])
{
	ObmContext obm;
	ObjClassRec classrec;
	int i;
	char *s;

	/* Initialize object manager global context. */
	obm = (ObmContext) XtCalloc (1, sizeof (struct obmContext));
	strcpy (obm->appname, "gterm-iraf");
	strcpy (obm->appclass, "Xgterm");
	obm->app_context = app_context;
	obm->argc = argc;
	obm->argv = argv;
	obm->debug = ((s = getenv("OBMDEBUG")) != NULL);
	if (s && (i = atoi(s)))
	    obm->debug = i;
	if ((s = getenv("OBMOBJECTS"))) {
	    obm->debug_objs = (char *) XtCalloc (1, strlen(s)+1);
	    strcpy (obm->debug_objs, s);
	} else
	    obm->debug_objs = (char *)NULL;
	    

	/* Initialize object classes. */
	for (i=0;  i < XtNumber(UiObjects);  i++) {
	    classrec = &UiObjects[i];
	    if (classrec->ClassInit)
		(*(classrec->ClassInit)) (obm, classrec);
	}

	/* Create new server and client objects. */
	ObmInitialize (obm);

	return (global_obm_handle = obm);
}


/* ObmClose -- Close the object manager.
 */
void
ObmClose (ObmContext obm)
{
	ObjClassRec classrec;
	int i;

	/* Get rid of any current UI. */
	ObmInitialize (obm);
	while (obm->head)
	    obmDestroyObject (obm, obm->head);

	/* Delete any callback descriptors. */
	while (obm->callback_list)
	    obmRemoveCallback (&obm->callback_list, obm->callback_list);

	/* Free any resources associated with the class descriptors. */
	for (i=0;  i < XtNumber(UiObjects);  i++) {
	    classrec = &UiObjects[i];
	    (*(classrec->ClassDestroy)) (obm, classrec);
	}

	if (obm->debug_objs)
	    XtFree ((char *)obm->debug_objs);
	XtFree ((char *)obm);
}


/* ObmInitialize -- Initialize the object manager, i.e., destroy any existing
 * user interface.
 */
void
ObmInitialize (ObmContext obm)
{
	ObmObject obj, nextobj;
	ObmCallback cb;
	MenuItem ip;
	ObjList lp, lp_next;
	Menu mp;
	int i;

        /* Destroy the UI object tree.  We need to be a little careful how
         * we do this as objects like to destroy child objects, execute
         * destroy callbacks, unmap windows, perform geometry requests, etc.
         * during destruction.  The widget class code can most efficiently
         * destroy the widget tree if destroy the toplevel widget first,
         * so we unmap the GUI and destroy the toplevel widget-class object
         * first.  Then we destroy whatever else is left.
         */
	obm->being_destroyed++;
	if ((obj = obmFindObject (obm, "toplevel"))) {
	    obmUndisplay (obm, obj);
	    obmDestroyObject (obm, obj);
	}
	while (obm->head)
	    obmDestroyObject (obm, obm->head);

	/* Free any cached pixmaps. */
	for (lp = obm->pixmap_cache;  lp;  lp = lp_next) {
	    lp_next = lp->next;
	    freeIcon (obm, (Icon *) lp->ptr);
	    XtFree ((char *)lp);
	}
	obm->pixmap_cache = NULL;

	/* Free any cached cursors. */
	for (lp = obm->cursor_cache;  lp;  lp = lp_next) {
	    lp_next = lp->next;
	    XFreeCursor (obm->display, (Cursor)lp->ptr);
	    XtFree ((char *)lp);
	}
	obm->cursor_cache = NULL;

	/* Free any menu lists. */
	for (lp = obm->menu_list;  lp;  lp = lp_next) {
	    lp_next = lp->next;
	    freeMenu ((MenuPtr) lp->ptr);
	}
	obm->menu_list = NULL;

	/* Close the application specific display connection. */
	if (obm->display) {
	    /* Call the client's display connection callbacks if any to
	     * inform the client that we are about to close the display.
	     */
	    for (cb = obm->callback_list;  cb;  cb = cb->next)
		if ((cb->callback_type & OBMCB_connect) && cb->u.fcn)
		    (*cb->u.fcn) (cb->client_data, obm->display, NULL, 0);

	    XFlush (obm->display);
	    XtCloseDisplay (obm->display);
	    obm->display = NULL;
	}

	/* Reinitialize the global context. */
	memset ((void *)obm->objindex, 0, sizeof(obm->objindex));
	strcpy (obm->appname, "gterm-iraf");
	strcpy (obm->appclass, "Xgterm");
	obm->head = obm->tail = NULL;
	obm->being_destroyed = 0;
	obm->toplevel = NULL;
	obm->specified = 0;
	obm->activated = 0;
	obm->mapped = 0;

	/* Free any callbacks that don't have the OBMCB_preserve flag set. */
	for (cb = obm->callback_list;  cb;  cb = cb->next)
	    if (!(cb->callback_type & OBMCB_preserve))
		obmRemoveCallback (&obm->callback_list, cb);

	/* Create new server and client objects. */
	obmNewObject (obm, "server", "Server", NULL, NULL, 0);
	obmNewObject (obm, "client", "Client", NULL, NULL, 0);
}


/* ObmActivate -- Activate the UI, i.e., ensure that it is active and in
 * such a state as to be ready for client i/o.
 */
void
ObmActivate (ObmContext obm)
{
	ObmObject obj;
	char defaultUI[SZ_MESSAGE];

	/* UI has already been activated? */
	if (obm->activated) {
	    if (!obm->mapped) {
		if ((obj = obmFindObject (obm, "toplevel")))
		    obmDisplay (obm, obj);
		obm->mapped++;

		/* Call activate callbacks after UI has been realized. */
		obm_call_activate_callbacks (obm, 1);
	    }
	    return;
	}

	if (!obm->specified) {
	    /* Construct a UI specification for the default UI. */
	    sprintf (defaultUI, "%s %s %s {%s.objects: %s%s%s%s%s}; %s; %s\n",
		"appInitialize", obm->appname, obm->appclass,
		 obm->appclass, "toplevel  Gterm  gterm\n",
		".geometry: 640x480\n",
		"*gterm.warpCursor: True\n",
		"*gterm.raiseWindow: True\n",
		"*gterm.deiconifyWindow: True\n",
		"createObjects",
		"send gterm setGterm\n");

	    /* Call the server to configure the default UI. */
	    ObmDeliverMsg (obm, "server", defaultUI);
	}

	/* Realize the toplevel widgets. */
	if ((obj = obmFindObject (obm, "toplevel")))
	    obmDisplay (obm, obj);

	obm->activated++;
	obm->mapped++;

	XFlush (obm->display);
	obm_call_activate_callbacks (obm, 1);
}


/* ObmDeactivate -- Deactivate the UI.  Optionally unmap the UI widget
 * tree and execute any deactivate callback registered by the Obm client.
 * Deactivation does not affect the state of the UI, i.e. a reactivate
 * will cause the UI to resume execution in the same state at which it
 * was deactivated.
 */
void
ObmDeactivate (ObmContext obm, Boolean unmap)
{
	ObmObject obj;
	ObmCallback cb;

	/* The Obm "activated" flag is not affected by deactivation.
	 * Deactivation merely means that control has temporarily been
	 * returned to the caller.  If the activate flag has not been set
	 * that means we do not yet have a UI to deactivate.
	 */
	if (!obm->activated)
	    return;

	/* Call any client deactivate callbacks before unrealizing the UI. */
	obm_call_activate_callbacks (obm, 0);

	if (unmap) {
	  if ((obj = obmFindObject (obm, "toplevel")))
		obmUndisplay (obm, obj);
	    obm->mapped = 0;
	}

	XFlush (obm->display);
}


/* obm_call_activate_callbacks -- Internal procedure to search the callback
 * lists and call any activate/deactivate callbacks.
 */
static void
obm_call_activate_callbacks (ObmContext obm, int state)
{
	ObmCallback cb;
	int type;

	/* Call any client activate callbacks. */
	type = state ? OBMCB_activate : OBMCB_deactivate;
	for (cb = obm->callback_list;  cb;  cb = cb->next)
	    if ((cb->callback_type & type) && cb->u.fcn)
		(*cb->u.fcn) (cb->client_data, obm->toplevel, state);

	/* Call any GUI activate callbacks. */
	type = state ? OBMUI_activate : OBMUI_deactivate;
	for (cb = obm->callback_list;  cb;  cb = cb->next)
	    if (cb->callback_type & type) {
		char message[SZ_NUMBER];
		int status;

		sprintf (message, "%d", state);
		status = Tcl_VarEval (obm->tcl,
		    cb->name, " ",
		    message, " ",
		    NULL); 

		if (status != TCL_OK) {
		    const char *errstr = Tcl_GetVar (obm->tcl, "errorInfo", 0);
		    fprintf (stderr, "Error on line %d in activate: %s\n",
			Tcl_GetErrorLine (obm->tcl),
			errstr ? errstr : Tcl_GetStringResult (obm->tcl));
		}
	    }
}


/* ObmActivated -- Test whether the GUI is activated, i.e., both defined
 * and mapped.
 */
int
ObmActivated (ObmContext obm)
{
	return (obm->activated && obm->mapped);
}


/* ObmStatus -- Get the Object Manager status.
 */
int
ObmStatus (ObmContext obm, char *app_name, char *app_class)
{
	if (obm->specified) {
	    if (app_name)
		strcpy (app_name, obm->appname);
	    if (app_class)
		strcpy (app_class, obm->appclass);

	    if (obm->activated)
		return (obm->mapped ? OBM_ACTIVE : OBM_IDLE);
	}

	return (OBM_INITIALIZED);
}


/* ObmGetInterp -- Get the main OBM (server object) interpreter.  This can
 * be used, e.g., by the calling program to extend the Tcl environment seen
 * by the GUI code.
 */
XtPointer
ObmGetInterp (ObmContext obm)
{
	return ((XtPointer)obm->tcl);
}


/* ObmDeliverMsg -- Deliver a message to a UI object.
 */
int
ObmDeliverMsg (ObmContext obm, const char *object, const char *message)
{
	ObmEvaluateFunc evaluate;
	ObmObject obj;
	int status = TCL_ERROR;

	if (obm->debug) {
	    if (!obm->debug_objs || strstr (obm->debug_objs, object) != NULL) {
	        printf ("%s: %s\n", object, message);
	        fflush (stdout);
	    }
	}
	    

        /* Note -- the following can execute appInitialize, which initializes
         * the server and creates a new Tcl, so no server context (such as
         * obm->tcl) should be stored in an internal variable here unless
	 * updated or not used again after the evaluate call.
         */
	Tcl_SetResult (obm->tcl, "", TCL_STATIC);
	if ((obj = obmFindObject (obm, object))) {
	    if ((evaluate = obj->core.classrec->Evaluate)) {
		status = (*evaluate)(obj, message);
		if (status != TCL_OK) {
		    const char *errstr = Tcl_GetVar (obm->tcl, "errorInfo", 0);
		    fprintf (stderr, "Error in message to %s, line %d: %s\n",
			object, Tcl_GetErrorLine (obm->tcl),
			     errstr ? errstr : Tcl_GetStringResult (obm->tcl));
		}
	    } else
		status = TCL_OK;
	} else {
	    Tcl_AppendResult (obm->tcl,
		"send: could not find object ", object, NULL);
	    status = TCL_ERROR;
	}
	    
	return (status);
}


/* ObmDeliverMsgFromFile -- Deliver a message to a UI object, taking the
 * message from the named text file.
 */
int
ObmDeliverMsgFromFile (ObmContext obm, char *object, char *fname)
{
	struct stat fs;
	char *message = NULL;
	int status, nchars;
	int fd = -1;

	if (stat (fname, &fs) >= 0) {
	    nchars = fs.st_size;
	    if ((message = (char *) XtMalloc (nchars + 1)) == NULL)
		goto err;
	    if ((fd = open (fname, 0)) < 0)
		goto err;
	    if (read (fd, message, nchars) != nchars)
		goto err;

	    message[nchars] = '\0';
	    status = ObmDeliverMsg (obm, object, message);

	    close (fd);
	    XtFree ((char *)message);
	    return (status);
	}
err:
	printf ("cannot access file %s\n", fname);
	if (fd >= 0)
	    close (fd);
	if (message)
	    XtFree ((char *)message);
	return (TCL_ERROR);
}


/* ObmAddCallback -- Add a callback of the given type to the OBM global
 * callback list.
 */
void
ObmAddCallback (ObmContext obm, int callback_type, ObmFunc fcn, XtPointer client_data)
{
	ObmCallback cb;

	if (!(cb = obmAddCallback (&obm->callback_list)))
	    return;

	cb->u.fcn = fcn;
	cb->callback_type = callback_type;
	cb->client_data = client_data;
	cb->name[0] = '\0';
}


/* ObmRemoveCallback -- Remove a callback from the OBM global callback list.
 */
void
ObmRemoveCallback (ObmContext obm, ObmCallback callback)
{
	obmRemoveCallback (&obm->callback_list, callback);
}


/*
 * Object manager internal routines.
 * ----------------------------------
 */

/* obmFindObject -- Lookup an object by name and return a pointer to the
 * associated object descriptor.  If the object name is unique only the object
 * name need be given, otherwise a name such as "parent1.parent2...object"
 * may be given to specify which object to use.
 */
ObmObject
obmFindObject (ObmContext obm, const char *object)
{
	int hashval, n;
	const char *ip;
	char *op;
	ObmObject objlist1[MAXOBJ], objlist2[MAXOBJ];
	ObmObject obj, *otemp, *objs, *pobjs;
	char name[SZ_NAME];
	int nobjs;

	if (object == NULL)
	    return (NULL);

	objs = objlist1;
	pobjs = objlist2;
	pobjs[0] = objs[0] = NULL;
	nobjs = 0; 

	for (ip=object;  *ip;  ) {
	    /* List of objects from last run becomes the list of parent objects
	     * for the current run.
	     */
	    otemp = pobjs;
	    pobjs = objs;
	    objs = otemp;

	    /* Get next object name. */
	    for (op=name;  *ip;  ) {
		if (*ip == '.') {
		    ip++;
		    break;
		} else
		    *op++ = *ip++;
	    }
	    *op = '\0';

	    /* Get list of candidate objects. */
	    if (obm_nameToObjectList (obm, name, pobjs, &nobjs, objs) == 0)
		return (NULL);
	}

	if (nobjs <= 0)
	    return (NULL);
	else if (nobjs > 1)
	    fprintf (stderr, "ambiguous object name: %s\n", object);

	return (objs[0]);
}


/* obm_nameToObjectList -- Return a list of objects with the given name which
 * are children of one of the (possibly empty) list of parent objects.  If the
 * parent object list is empty the list of all objects with the given name
 * is returned.
 */
int
obm_nameToObjectList (
  ObmContext obm,
  char *object,			/* object name */
  ObmObject *pobjs,		/* list of parent objects */
  int *nobjs,			/* number of objects found (output) */
  ObmObject *objs)		/* list of objects (output) */
{
	char *ip;
	int hashval, n;
	ObmObject obj;
	int accept, i;

	if (object == NULL)
	    return (0);

	/* Compute hash value. */
	for (hashval=0, ip=object, n=MAX_HASHCHARS;  --n >= 0 && *ip;  ip++)
	    hashval += (hashval + *ip);
	if (hashval < 0)
	    return (0);

	/* Examine any objects on the hash thread. */
	obj = obm->objindex[hashval%SZ_INDEX];
	for (n=0;  obj;  obj=obj->core.nexthash) {
	    if (strcmp (object, obj->core.name) == 0) {
		accept = 0;
		if (pobjs && pobjs[0]) {
		    for (i=0;  pobjs[i];  i++)
			if (obj->core.parent == pobjs[i]) {
			    accept++;
			    break;
			}
		} else
		    accept++;

		if (accept)
		    objs[n++] = obj;
	    }
	}

	objs[n] = NULL;
	return (*nobjs = n);
}


/* obmNewObject -- Create a new object of the given type.
 */
void
obmNewObject (
  ObmContext obm,
  const char *name,		/* name of new object */
  const char *class,		/* name of class to which object belongs */
  const char *parent,		/* name of parent widget, for widget objects */
  ArgList args,			/* optional argument list */
  int nargs)			/* optional argument list */
{
	const char *ip;
	int hashval, n;
	ObmObject newobj, obj, pobj;
	ObjClassRec classrec;

	if (obm->being_destroyed)
	    return;

	/* Ignore the request if parent is being destroyed. */
	pobj = obmFindObject (obm, parent);
	if (parent && (!pobj || pobj->core.being_destroyed))
	    return;

	/* Get class record. */
	if (!(classrec = obmGetClassrec (class))) {
	    fprintf (stderr, "obm: object %s has unknown class %s\n",
		name, class);
	    return;
	}

	/* Create the object. */
	    
	newobj = (*(classrec->Create)) (obm,
	    name, classrec, parent, args, nargs);
	if (!newobj) {
	    fprintf (stderr, "obm: could not create object %s class %s\n",
		name, class);
	    return;
	}

	strcpy (newobj->core.name, name);
	newobj->core.classrec = classrec;
	newobj->core.parent = pobj;

	/* Link the object into the object list. */
	if (!obm->head)
	    obm->head = newobj;
	if ((newobj->core.prevglob = obm->tail))
	    obm->tail->core.nextglob = newobj;
	obm->tail = newobj;

	/* Compute hash value. */
	for (hashval=0, ip=name, n=MAX_HASHCHARS;  --n >= 0 && *ip;  ip++)
	    hashval += (hashval + *ip);

	/* Enter the object into the hash table. */
	if ((obj = obm->objindex[hashval%SZ_INDEX])) {
	    while (obj->core.nexthash)
		obj = obj->core.nexthash;
	    obj->core.nexthash = newobj;
	} else
	    obm->objindex[hashval%SZ_INDEX] = newobj;

	/* Add the object to the parent's list of children. */
	if (parent) {
	    ObmObject pobj;
	    ObmObjectCore cp;
	    
	    if ((pobj = obmFindObject (obm, parent)) == NULL) {
		fprintf (stderr, "obm: object %s has unknown parent %s\n",
		    name, parent);
		return;
	    } else
		cp = &pobj->core;

	    if (cp->nchildren) {
		cp->children = (ObmObject *) XtRealloc ((char *)cp->children,
		    (cp->nchildren + 1) * sizeof(ObmObject));
	    } else
		cp->children = (ObmObject *) XtMalloc (sizeof(ObmObject));

	    cp->children[cp->nchildren++] = newobj;
	}
}


/* obmDestroyObject -- Destroy an object and all its descendents.
 */
void
obmDestroyObject (ObmContext obm, ObmObject object)
{
	ObmObjectCore cp = &object->core;
	ObmObject obj;
	int i;
	int hashval, n;
	char *ip;

	if (!object)
	    return;
	if (object->core.being_destroyed)
	    return;

	/* Destroy the object instance itself.  The object destroy class
	 * method is called twice when an object is destroyed, once at the
	 * beginning before any children are destroyed and once at the end
	 * after all children have been destroyed and the object descriptor
	 * has been unlinked from all Obm object data structures.  It is
	 * up to the Destroy class method for the object to decide how much
	 * of the object to destroy in each call.  Most objects just set a
	 * being_destroyed flag in the first call and wait until the end
	 * to destroy the object, in case the object descriptor is referenced
	 * during the destroy process.  An example of a class which does
	 * things differently is the widget class.  In the case of a widget
	 * the first call destroys the widget and all of its children, even
	 * though the object descriptors are not freed until the remainder of
	 * the code below is executed.  This allows the window system toolkit
	 * code to determine the best way to destroy a widget hierarchy,
	 * rather than having us destroy each widget one by one from the
	 * bottom up.
	 */
	(*(object->core.classrec->Destroy)) (object);

	/* Destroy any children.  Note that each time a child is destroyed the
	 * child list in the object descriptor is modified, so we merely loop
	 * until nchildren goes to zero.
	 */
	if (cp->nchildren) {
	    while (cp->nchildren > 0)
		obmDestroyObject (obm, cp->children[0]);
	}

	/* Remove this object from the child list of the parent. */
	if ((obj = object->core.parent)) {
	    if (obj->core.nchildren == 1) {
		XtFree ((char *)obj->core.children);
		obj->core.nchildren = 0;

	    } else {
		int nchild = obj->core.nchildren;
		ObmObject *new_list, *ip, *op;

		new_list = (ObmObject *) XtMalloc (nchild * sizeof(ObmObject));
		op = new_list;

		for (ip = obj->core.children;  --nchild >= 0;  ip++)
		    if (*ip != object)
			*op++ = *ip;

		XtFree ((char *)obj->core.children);
		obj->core.children = new_list;
		obj->core.nchildren--;
	    }
	}

	/* Unlink the object from the global object list. */
	if ((obj = object->core.prevglob)) {
	    if (!(obj->core.nextglob = object->core.nextglob))
		obm->tail = obj;
	} else
	    obm->head = object->core.nextglob;

	if ((obj = object->core.nextglob)) {
	    if (!(obj->core.prevglob = object->core.prevglob))
		obm->head = obj;
	} else
	    obm->tail = object->core.prevglob;

	/* Unlink the object from the hash list. */
	ip = object->core.name;
	for (hashval=0, n=MAX_HASHCHARS;  --n >= 0 && *ip;  ip++)
	    hashval += (hashval + *ip);
	if ((obj = obm->objindex[hashval%SZ_INDEX])) {
	    while (obj && obj->core.nexthash != object)
		obj = obj->core.nexthash;
	    if (obj)
		obj->core.nexthash = object->core.nexthash;
	    else
		obm->objindex[hashval%SZ_INDEX] = NULL;
	}

	/* Free the object descriptor. */
	(*(object->core.classrec->Destroy)) (object);
	XtFree ((char *)object);
}


/* obmDisplay -- Display an entire user interface, including all top level
 * shells.
 */
void
obmDisplay (ObmContext obm, ObmObject obj)
{
	Widget w = widgetGetPointer (obj);
	ObmObject child;
	int i;
	char buf[SZ_NAME];

	for (i=0;  i < obj->core.nchildren;  i++) {
	    child = obj->core.children[i];
	    if (child->core.classrec->object_type == OtShell)
		obmDisplay (obm, child);
	}

	/* The following isn't used anymore. */
	if (obj->core.geometry[0])
	    XtVaSetValues (w, XtNgeometry, obj->core.geometry, NULL);

	XtRealizeWidget (w);
	if (obj->core.mapped)
	    XtMapWidget (w);
}


/* obmUndisplay -- Undisplay an entire user interface, including all top level
 * shells.
 */
void
obmUndisplay (ObmContext obm, ObmObject obj)
{
	int i;
	ObmObject child;
	Widget w = widgetGetPointer (obj);
	XWindowAttributes wa;
	char *s;

	for (i=0;  i < obj->core.nchildren;  i++) {
	    child = obj->core.children[i];
	    if (child->core.classrec->object_type == OtShell)
		obmUndisplay (obm, child);
	}

	if (XtWindow(w)) {
	    /* The following isn't used anymore. */
	    if ((s = get_geometry (obm->display, obm->screen, XtWindow(w), 1)))
		strcpy (obj->core.geometry, s);
	    if (XGetWindowAttributes (obm->display, XtWindow(w), &wa))
		obj->core.mapped = (wa.map_state != IsUnmapped);
	    /* XtUnrealizeWidget (w); */

	    /* Unrealizing the widgets is too drastic, the following merely
	     * makes the window and icon disappear.
	     */
	    XmuUpdateMapHints (obm->display, XtWindow(w), NULL);
	    XWithdrawWindow (obm->display, XtWindow(w),
		XScreenNumberOfScreen(obm->screen));
	}
}


/* obmGetClassrec -- Get the class record for the named class.
 */
ObjClassRec
obmGetClassrec (const char *classname)
{
	ObjClassRec classrec;
	int i;

	for (i=0;  i < XtNumber(UiObjects);  i++) {
	    classrec = &UiObjects[i];
	    if (strcmp (classname, classrec->name) == 0)
		return (classrec);
	}

	return (NULL);
}


/* obmGenericClassDestroy - Free any resources associated with a class record.
 */
void
obmGenericClassDestroy (ObmContext obm, ObjClassRec classrec)
{
}


/* obmClass -- Test if a class record belongs to the given object class.
 */
int
obmClass (ObjClassRec classrec, unsigned long flag1, unsigned long flag2)
{
	return ((classrec->flag1 & flag1) || (classrec->flag2 & flag2));
}


/* obmAddCallback -- Add a callback descriptor to a callback list.
 */
ObmCallback
obmAddCallback (ObmCallback *callback_list)
{
	ObmCallback cb, last_cb;

	/* Find tail of list. */
	for (cb = last_cb = *callback_list;  cb;  cb = cb->next)
	    last_cb = cb;

	if (!(cb = (ObmCallback) XtCalloc (1, sizeof (obmCallback))))
	    return (NULL);

	if (!last_cb)
	    *callback_list = cb;
	else
	    last_cb->next = cb;

	return (cb);
}


/* obmRemoveCallback -- Remove a callback descriptor from a callback list.
 */
void
obmRemoveCallback (ObmCallback *callback_list, ObmCallback callback)
{
	ObmCallback cb, last_cb;

	if (!callback)
	    return;

	/* Search for named callback descriptor. */
	for (cb = *callback_list, last_cb = NULL;  cb;  cb = cb->next) {
	    if (cb == callback)
		break;
	    else
		last_cb = cb;
	}

	if (last_cb)
	    last_cb->next = cb->next;
	else
	    *callback_list = cb->next;

	XtFree ((char *) cb);
}


/* obmDefined -- Test if the named function is a defined client function in
 * the given Tcl interepter.
 */
int
obmClientCommand (Tcl_Interp *tcl, const char *commmand)
{
	const char *ip;
	char *op;
	char name[SZ_NAME];
	Tcl_CmdInfo info;

	/* Get command name.  This works even if we are passed a command
	 * line including arguments.
	 */
	for (ip=commmand;  *ip && isspace(*ip);  ip++)
	    ;
	for (op=name;  *ip && !isspace(*ip) && *ip != ';';  )
	    *op++ = *ip++;
	*op = '\0';
	if (op - name == 0)
	    return (False);

	/* Test if the named client command exists.  This assumes that
	 * client commands always have the clientData field set to a
	 * non-NULL value.  Tcl (at least in the current version) sets
	 * this field to NULL for the builtin commands.
	 */
	if (Tcl_GetCommandInfo (tcl, name, &info))
	    return (info.clientData != NULL);
	else
	    return (False);
}
