/*
    This file is part of the FElt finite element analysis package.
    Copyright (C) 1993 Jason I. Gobat and Darren C. Atkinson

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/************************************************************************
 * File:	node.c							*
 *									*
 * Description:	This file contains the public and private function and	*
 *		type definitions for the node dialog box.		*
 ************************************************************************/

# include <stdio.h>
# include <X11/Intrinsic.h>
# include <X11/StringDefs.h>
# include <X11/Shell.h>
# include <X11/Xaw/AsciiText.h>
# include <X11/Xaw/Command.h>
# include <X11/Xaw/Label.h>
# include <X11/Xaw/MenuButton.h>
# include <X11/Xaw/Repeater.h>
# include <X11/Xaw/SimpleMenu.h>
# include <X11/Xaw/SmeBSB.h>
# include "Layout.h"
# include "Node.h"
# include "TabGroup.h"
# include "util.h"
# include "objects.h"
# include "post.h"

# ifndef X_NOT_STDC_ENV
# include <stdlib.h>
# else
extern double atof ( );
# endif


struct node_dialog {
    Widget         shell;	/* topLevelShell  <specified>		  */
    Widget         layout;	/*	Layout  layout			  */
    Widget         number;	/*	     Label  number		  */
    Widget         up;		/*	     Repeater  up		  */
    Widget         down;	/*	     Repeater  down		  */
    Widget         f_name;	/*	     AsciiText  forceName	  */
    Widget         f_button;	/*	     MenuButton  forceButton	  */
    Widget         f_menu;	/*	     SimpleMenu  forceMenu	  */
    Widget         c_name;	/*	     AsciiText  constraintName	  */
    Widget         c_button;	/*	     MenuButton  constraintButton */
    Widget         c_menu;	/*	     SimpleMenu  constraintMenu	  */
    Widget         loc_x;	/*	     AsciiText  locationX	  */
    Widget         loc_y;	/*	     AsciiText  locationY	  */
    Widget         loc_z;	/*	     AsciiText  locationZ	  */
    Widget         trans_x;	/*	     AsciiText  translationX	  */
    Widget         trans_y;	/*	     AsciiText  translationY	  */
    Widget         trans_z;	/*	     AsciiText  translationZ	  */
    Widget         rot_x;	/*	     AsciiText  rotationX	  */
    Widget         rot_y;	/*	     AsciiText  rotationY	  */
    Widget         rot_z;	/*	     AsciiText  rotationZ	  */
    Widget         help;	/*	     MenuButton  help		  */
    Widget         accept;	/*	     Command  accept		  */
    Widget         dismiss;	/*	     Command  dismiss		  */
    Widget         delete;	/*	     Command  delete		  */
    Widget         new;		/*	     Command  new		  */
    Widget         copy;	/*	     Command  copy		  */
    XtCallbackProc callback;
    XtPointer	   closure;
    unsigned	   new_copy;
    Boolean	   new_forces;
    Boolean	   new_constraints;
    Node           active;
    Tree           nodes;
    Tree           forces;
    Tree           constraints;
};

static String labels [ ] = {
    "Number:", "X:", "Y:", "Z:", "Location", "Translation", "Rotation"
};

static String names [ ] = {
    "numberLabel", "x", "y", "z", "location", "translation", "rotation"
};


/* Bitmaps */

#define up_width 12
#define up_height 12
static char up_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0xf0, 0x00, 0xf8, 0x01,
   0xfc, 0x03, 0xfe, 0x07, 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

#define down_width 12
#define down_height 12
static char down_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0xfe, 0x07, 0xfc, 0x03,
   0xf8, 0x01, 0xf0, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static Pixmap up_bitmap;
static Pixmap down_bitmap;


/* Resources */

static Pixel highlight;

static String layout_string = "\
vertical { \
    4 \
    horizontal { \
	4 \
	up \
	4 \
	down \
	4 \
	numberLabel \
	4 \
	number \
	4 <+inf -100%> \
    } \
    4 \
    separator1 <+inf -100% *> \
    4 \
    horizontal { \
	4 \
	forceButton \
	4 \
	forceName <+inf -100% *> \
	4 \
	constraintButton \
	4 \
	constraintName <+inf -100% *> \
	4 \
    } \
    4 \
    separator2 <+inf -100% *> \
    4 \
    horizontal { \
	4 \
	vertical { \
	    height location \
	    4 \
	    ((height locationX - height x) / 2) \
	    x \
	    ((height locationX - height x) / 2) \
	    4 \
	    ((height locationY - height y) / 2) \
	    y \
	    ((height locationY - height y) / 2) \
	    4 \
	    ((height locationZ - height z) / 2) \
	    z \
	    ((height locationZ - height z) / 2) \
	} \
	4 \
	vertical { \
	    location <+inf -100% *> \
	    4 \
	    locationX <+inf -100% *> \
	    4 \
	    locationY <+inf -100% *> \
	    4 \
	    locationZ <+inf -100% *> \
	} \
	4 \
	vertical { \
	    translation <+inf -100% *> \
	    4 \
	    translationX <+inf -100% *> \
	    4 \
	    translationY <+inf -100% *> \
	    4 \
	    translationZ <+inf -100% *> \
	} \
	4 \
	vertical { \
	    rotation <+inf -100% *> \
	    4 \
	    rotationX <+inf -100% *> \
	    4 \
	    rotationY <+inf -100% *> \
	    4 \
	    rotationZ <+inf -100% *> \
	} \
	4 \
    } \
    4 \
    separator3 <+inf -100% *> \
    4 \
    horizontal { \
	4 \
	help \
	4 <+inf -100%> \
	accept \
	4 <+inf -100%> \
	dismiss \
	4 <+inf -100%> \
	delete \
	4 <+inf -100%> \
	new \
	4 <+inf -100%> \
	copy \
	4 \
    } \
    4 \
}";

static Arg color_args [ ] = {
    {XtNborderColor, (XtArgVal) &highlight},
};

static Arg shell_args [ ] = {
    {XtNtitle,    (XtArgVal) NULL},
    {XtNiconName, (XtArgVal) NULL},
};

static Arg layout_args [ ] = {
    {XtNlayout, (XtArgVal) NULL},
};

static Arg edit_text_args [ ] = {
    {XtNeditType,    (XtArgVal) XawtextEdit},
    {XtNborderWidth, (XtArgVal) 0},
    {XtNpieceSize,   (XtArgVal) 32},
    {XtNcursorName,  (XtArgVal) "left_ptr"},
};

static Arg read_text_args [ ] = {
    {XtNeditType,     (XtArgVal) XawtextRead},
    {XtNpieceSize,    (XtArgVal) 32},
    {XtNcursorName,   (XtArgVal) "left_ptr"},
    {XtNdisplayCaret, (XtArgVal) False},
};

static Arg button_args [ ] = {
    {XtNlabel,       (XtArgVal) ""},
    {XtNmenuName,    (XtArgVal) ""},
    {XtNborderWidth, (XtArgVal) 0},
};

static Arg number_args [ ] = {
    {XtNlabel,       (XtArgVal) ""},
    {XtNresize,      (XtArgVal) True},
    {XtNborderWidth, (XtArgVal) 0},
};

static Arg label_args [ ] = {
    {XtNlabel,       (XtArgVal) ""},
    {XtNborderWidth, (XtArgVal) 0},
};

static Arg core_args [ ] = {
    {XtNwidth,  (XtArgVal) 1},
    {XtNheight, (XtArgVal) 1},
};

static Arg repeater_args [ ] = {
    {XtNbitmap, (XtArgVal) NULL},
};


/* Translation tables */

static String text_table =
"<Key>Return: NodeDialogAction(accept)\n\
 <Key>Escape: NodeDialogAction(dismiss)\n\
 Ctrl<Key>d:  NodeDialogAction(delete)\n\
 Ctrl<Key>c:  NodeDialogAction(copy)\n\
 Ctrl<Key>n:  NodeDialogAction(new)\n\
 Ctrl<Key>h:  NodeDialogAction(help)\n\
 <Btn1Down>:  SetFocus() select-start()";

static XtTranslations text_translations;


static String command_table =
"<Key>Return:  NodeDialogAction(accept)\n\
 <Key>Escape:  NodeDialogAction(dismiss)\n\
 Ctrl<Key>d:   NodeDialogAction(delete)\n\
 Ctrl<Key>c:   NodeDialogAction(copy)\n\
 Ctrl<Key>n:   NodeDialogAction(new)\n\
 Ctrl<Key>h:   NodeDialogAction(help)\n\
 <Key>space:   AutoRepeat(off) set()\n\
 <KeyUp>space: AutoRepeat(saved) notify() unset()";

static XtTranslations command_translations;


static String button_table =
"<Key>Return: NodeDialogAction(accept)\n\
 <Key>Escape: NodeDialogAction(dismiss)\n\
 Ctrl<Key>d:  NodeDialogAction(delete)\n\
 Ctrl<Key>c:  NodeDialogAction(copy)\n\
 Ctrl<Key>n:  NodeDialogAction(new)\n\
 Ctrl<Key>h:  NodeDialogAction(help)\n\
 <BtnDown>:   PostMenu()\n\
 <Key>space:  PostMenu()";

static XtTranslations button_translations;


static String repeater_table =
"<Key>Return:  NodeDialogAction(accept)\n\
 <Key>Escape:  NodeDialogAction(dismiss)\n\
 Ctrl<Key>d:   NodeDialogAction(delete)\n\
 Ctrl<Key>c:   NodeDialogAction(copy)\n\
 Ctrl<Key>n:   NodeDialogAction(new)\n\
 Ctrl<Key>h:   NodeDialogAction(help)\n\
 <Key>space:   AutoRepeat(off) set()\n\
 <KeyUp>space: AutoRepeat(saved) notify() unset()";

static XtTranslations repeater_translations;


static String help_table =
"<Key>Return: NodeDialogAction(accept)\n\
 <Key>Escape: NodeDialogAction(dismiss)\n\
 Ctrl<Key>d:  NodeDialogAction(delete)\n\
 Ctrl<Key>c:  NodeDialogAction(copy)\n\
 Ctrl<Key>n:  NodeDialogAction(new)\n\
 Ctrl<Key>h:  NodeDialogAction(help)\n\
 <Key>space:  PostMenu()";

static XtTranslations help_translations;


/* Help message. */

static String help_message = "\
The node form is used to define, edit and delete nodes.  Nodes can also be \
selected by using the drawing area.  Use the arrows to change the current \
node.  The menus and fields above define the behavior of the current node.  \
Use the 'Accept' button to register your changes. 'Delete' erases the current \
node.  'New' empties all fields creating a new node.  'Copy' creates a new \
node without changing any fields.";


/* Menu creation variables */

static Cardinal		child_number;
static WidgetList	children;
static Dimension	max_width;
static XtWidgetGeometry	preferred;


/************************************************************************
 * Function:	SetForceEntry						*
 *									*
 * Description:	Sets the label of the next menu entry to the name of	*
 *		the specified force.					*
 ************************************************************************/

static int SetForceEntry (item)
    Item item;
{
    SetLabelString (children [child_number], ((Force) item) -> name);

    XtQueryGeometry (children [child_number ++], NULL, &preferred);
    if (preferred.width > max_width)
	max_width = preferred.width;

    return 0;
}


/************************************************************************
 * Function:	UpdateForceMenu						*
 *									*
 * Description:	Updates the force menu on popup if necessary to the	*
 *		list of current forces.					*
 ************************************************************************/

static void UpdateForceMenu (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Arg        args [2];
    char       buffer [32];
    Cardinal   overflow;
    Cardinal   num_forces;
    Cardinal   i;
    Cardinal   num_children;
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    if (noded -> new_forces == False)
	return;

    XtSetArg (args [0], XtNchildren,    &children);
    XtSetArg (args [1], XtNnumChildren, &num_children);
    XtGetValues (w, args, 2);

    num_forces = TreeSize (noded -> forces) + 1;

    for (i = num_children; i < num_forces; i ++) {
	sprintf (buffer, "force%d", i);
	XtCreateManagedWidget (XtNewString (buffer), smeBSBObjectClass,
			       noded -> f_menu, NULL, 0);
    }

    if (num_children > num_forces) {
	overflow = num_children - num_forces;
	XtUnmanageChildren (children + num_forces, overflow);
    }

    XtSetArg (args [0], XtNchildren,    &children);
    XtSetArg (args [1], XtNnumChildren, &num_children);
    XtGetValues (w, args, 2);

    XtQueryGeometry (children [0], NULL, &preferred);
    max_width = preferred.width;

    child_number = 1;
    TreeSetIterator (noded -> forces, SetForceEntry);
    TreeIterate (noded -> forces);
    noded -> new_forces = False;

    XtSetArg (args [0], XtNwidth, max_width);
    XtSetValues (w, args, 1);
}


/************************************************************************
 * Function:	UpdateForceName						*
 *									*
 * Description:	Updates the force name on popdown if necessary.		*
 ************************************************************************/

static void UpdateForceName (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    NodeDialog noded;


    if (!(w = XawSimpleMenuGetActiveEntry (w)))
	return;

    noded = (NodeDialog) client_data;

    if (!strcmp (XtName (w), "- none -"))
	SetTextString (noded -> f_name, "");
    else
	SetTextString (noded -> f_name, GetLabelString (w));
}


/************************************************************************
 * Function:	SetConstraintEntry					*
 *									*
 * Description:	Sets the label of the next menu entry to the name of	*
 *		the specified constraint.				*
 ************************************************************************/

static int SetConstraintEntry (item)
    Item item;
{
    SetLabelString (children [child_number], ((Constraint) item) -> name);

    XtQueryGeometry (children [child_number ++], NULL, &preferred);
    if (preferred.width > max_width)
	max_width = preferred.width;

    return 0;
}


/************************************************************************
 * Function:	UpdateConstraintMenu					*
 *									*
 * Description:	Updates the constraint menu on popup if necessary to	*
 *		the list of current constraints.			*
 ************************************************************************/

static void UpdateConstraintMenu (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Arg        args [2];
    char       buffer [32];
    Cardinal   overflow;
    Cardinal   num_constraints;
    Cardinal   i;
    Cardinal   num_children;
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    if (noded -> new_constraints == False)
	return;

    XtSetArg (args [0], XtNchildren,    &children);
    XtSetArg (args [1], XtNnumChildren, &num_children);
    XtGetValues (w, args, 2);

    num_constraints = TreeSize (noded -> constraints);

    if (num_constraints <= 0) {
	num_constraints ++;
	SetLabelString (children [0], "- no constraints defined -");
	XtQueryGeometry (children [0], NULL, &preferred);
	max_width = preferred.width;
    } else
	max_width = 0;

    for (i = num_children; i < num_constraints; i ++) {
	sprintf (buffer, "constraint%d", i);
	XtCreateManagedWidget (XtNewString (buffer), smeBSBObjectClass,
			       noded -> c_menu, NULL, 0);
    }

    if (num_children > num_constraints) {
	overflow = num_children - num_constraints;
	XtUnmanageChildren (children + num_constraints, overflow);
    }

    XtSetArg (args [0], XtNchildren,    &children);
    XtSetArg (args [1], XtNnumChildren, &num_children);
    XtGetValues (w, args, 2);

    child_number = 0;
    TreeSetIterator (noded -> constraints, SetConstraintEntry);
    TreeIterate (noded -> constraints);
    noded -> new_constraints = False;

    XtSetArg (args [0], XtNwidth, max_width);
    XtSetValues (w, args, 1);
}


/************************************************************************
 * Function:	UpdateConstraintName					*
 *									*
 * Description:	Updates the constraint name on popdown if necessary.	*
 ************************************************************************/

static void UpdateConstraintName (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    String     label;
    NodeDialog noded;


    if (!(w = XawSimpleMenuGetActiveEntry (w)))
	return;

    noded = (NodeDialog) client_data;
    label = GetLabelString (w);

    if (!strcmp (label, "- no constraints defined -"))
	SetTextString (noded -> c_name, "");
    else
	SetTextString (noded -> c_name, label);
}


/************************************************************************
 * Function:	SetNumber						*
 *									*
 * Description: Sets the label of the number widget.			*
 ************************************************************************/

static void SetNumber (noded, number)
    NodeDialog noded;
    unsigned   number;
{
    char buffer [10];


    sprintf (buffer, "%u", number);
    SetLabelString (noded -> number, buffer);
}


/************************************************************************
 * Function:	Action							*
 *									*
 * Description:	An action procedure which emulates the pressing of the	*
 *		specified button.					*
 ************************************************************************/

static void Action (w, event, params, num_params)
    Widget    w;
    XEvent   *event;
    String   *params;
    Cardinal *num_params;
{
    if (XtClass (w) == topLevelShellWidgetClass)
	w = XtNameToWidget (w, "layout.dismiss");
    else
	w = XtNameToWidget (XtParent (w), params [0]);

    if (!strcmp (XtName (w), "help"))
	XtCallActionProc (w, "PostMenu", event, NULL, 0);
    else
	XtCallCallbacks (w, XtNcallback, NULL);
}


/************************************************************************
 * Function:	Up							*
 *									*
 * Description:	Displays the next highest numbered node.		*
 ************************************************************************/

static void Up (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Node       node;
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    if (noded -> active == NULL)
	return;

    node = (Node) TreeSuccessor (noded -> nodes, noded -> active);

    if (node != NULL)
	NodeDialogDisplay (noded, node);
}


/************************************************************************
 * Function:	Down							*
 *									*
 * Description:	Displays the next lowest numbered node.			*
 ************************************************************************/

static void Down (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Node       node;
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    if (noded -> active == NULL)
	return;

    node = (Node) TreePredecessor (noded -> nodes, noded -> active);

    if (node != NULL)
	NodeDialogDisplay (noded, node);
}


/************************************************************************
 * Function:	Accept							*
 *									*
 * Description:	Accepts changes made to the currently displayed force.	*
 *		If the constraint is empty or the constraint or force	*
 *		does not exist then an error is reported.  Otherwise, a	*
 *		new node is created if a new/copy operation is in	*
 *		effect.  The node is then redisplayed to correct any	*
 *		invalid entries.					*
 ************************************************************************/

static void Accept (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    double	      x;
    double	      y;
    Constraint	      constraint;
    struct force      f_dummy;
    struct constraint c_dummy;
    Node	      active;
    NodeDialog	      noded;
    NodeDialogInfo    info;


    noded = (NodeDialog) client_data;


    /* Retrieve the constraint. */

    c_dummy.name = GetTextString (noded -> c_name);
    constraint = (Constraint) TreeSearch (noded -> constraints, &c_dummy);
    if (constraint == NULL) {
	XBell (XtDisplay (noded -> shell), 0);
	SetTextString (noded -> c_name, "");
	SetFocus (noded -> c_name);
	return;
    }


    /* Create a new node as needed. */

    if (noded -> new_copy) {
	noded -> active = CreateNode (noded -> new_copy);
	TreeInsert (noded -> nodes, noded -> active);
    }

    active = noded -> active;
    active -> constraint = constraint;


    /* Retrieve the force. */

    f_dummy.name = GetTextString (noded -> f_name);
    active -> force = (Force) TreeSearch (noded -> forces, &f_dummy);


    /* Retrieve the values from the text entries. */

    x = atof (GetTextString (noded -> loc_x));
    y = atof (GetTextString (noded -> loc_y));

    info.moved = x != active -> x || y != active -> y;
    active -> x = x;
    active -> y = y;

    if (noded -> callback != NULL) {
	info.dialog  = noded;
	info.node    = active;
	info.deleted = False;
	info.proceed = True;
	noded -> callback (noded -> shell, noded -> closure, &info);
    }

    NodeDialogUpdate (noded, noded -> nodes, NULL, NULL);
}


/************************************************************************
 * Function:	Dismiss							*
 *									*
 * Description:	Pops down the dialog box.				*
 ************************************************************************/

static void Dismiss (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    NodeDialog noded;


    noded = (NodeDialog) client_data;
    XtPopdown (noded -> shell);
}


/************************************************************************
 * Function:	Delete							*
 *									*
 * Description:	Deletes the active node if a new/copy operation is not	*
 *		in effect.  The dialog is then updated.			*
 ************************************************************************/

static void Delete (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Node	   node;
    NodeDialog	   noded;
    NodeDialogInfo info;


    noded = (NodeDialog) client_data;

    if (!noded -> new_copy) {
	if (noded -> callback) {
	    w = noded -> shell;
	    info.dialog  = noded;
	    info.node    = noded -> active;
	    info.deleted = True;
	    info.proceed = True;
	    noded -> callback (w, noded -> closure, &info);
	    if (info.proceed == False)
		return;
	}

	if (!(node = (Node) TreePredecessor (noded -> nodes, noded -> active)))
	    node = (Node) TreeSuccessor (noded -> nodes, noded -> active);

	TreeDelete (noded -> nodes, noded -> active);
	DestroyNode (noded -> active);
	noded -> active = node;
    }

    NodeDialogUpdate (noded, noded -> nodes, NULL, NULL);
}


/************************************************************************
 * Function:	Copy							*
 *									*
 * Description:	Selects a new node number, clears the displacement	*
 *		fields, and sets the flag indicating that a new/copy	*
 *		operation is in effect.					*
 ************************************************************************/

static void Copy (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    Node       node;
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    node = (Node) TreeMaximum (noded -> nodes);
    noded -> new_copy = node != NULL ? node -> number + 1 : 1;

    SetNumber (noded, noded -> new_copy);

    SetTextString (noded -> trans_x, "");
    SetTextString (noded -> trans_y, "");
    SetTextString (noded -> trans_z, "");
    SetTextString (noded -> rot_x, "");
    SetTextString (noded -> rot_y, "");
    SetTextString (noded -> rot_z, "");
}


/************************************************************************
 * Function:	New							*
 *									*
 * Description:	Selects a new node number, clears all entries in the	*
 *		node dialog, and sets the flag indicating that a new/	*
 *		copy operation is in effect.				*
 ************************************************************************/

static void New (w, client_data, call_data)
    Widget    w;
    XtPointer client_data;
    XtPointer call_data;
{
    NodeDialog noded;


    noded = (NodeDialog) client_data;

    Copy (NULL, client_data, NULL);
    SetTextString (noded -> f_name, "");
    SetTextString (noded -> c_name, "");
    SetTextString (noded -> loc_x, "");
    SetTextString (noded -> loc_y, "");
    SetTextString (noded -> loc_z, "");
}


/************************************************************************
 * Function:	NodeDialogCreate					*
 *									*
 * Description:	Creates a new node dialog.  You would never want to	*
 *		have more than one of these but the interface is kept	*
 *		consistent with those of the other dialogs.		*
 ************************************************************************/

NodeDialog NodeDialogCreate (parent, name, title, callback, closure)
    Widget         parent;
    String         name;
    String         title;
    XtCallbackProc callback;
    XtPointer	   closure;
{
    Cardinal		i;
    Arg			args [1];
    Widget		group [15];
    Window		window;
    NodeDialog		noded;
    Dimension		width;
    Position		x;
    static XtAppContext	app_context = NULL;
    static XtActionsRec	actions [ ] = {{"NodeDialogAction", Action}};


    /* Perform one time initialization. */

    if (app_context == NULL) {
	app_context = XtWidgetToApplicationContext (parent);
	XtAppAddActions (app_context, actions, XtNumber (actions));
	AddAutoRepeatAction (app_context);

	layout_args [0].value = StringToLayout (parent, layout_string);

	text_translations = XtParseTranslationTable (text_table);
	button_translations = XtParseTranslationTable (button_table);
	command_translations = XtParseTranslationTable (command_table);
	repeater_translations = XtParseTranslationTable (repeater_table);
	help_translations = XtParseTranslationTable (help_table);

	window = RootWindowOfScreen (XtScreen (parent));

	up_bitmap = XCreateBitmapFromData (XtDisplay (parent), window,
		    up_bits, up_width, up_height);

	down_bitmap = XCreateBitmapFromData (XtDisplay (parent), window,
		      down_bits, down_width, down_height);
    }


    /* Create the node dialog and its widgets. */

    XtSetArg (shell_args [0], XtNtitle, title);
    XtSetArg (shell_args [1], XtNiconName, title);

    noded = XtNew (struct node_dialog);
    noded -> callback = callback;
    noded -> closure = closure;
    noded -> active = NULL;
    noded -> new_forces = True;
    noded -> new_constraints = True;

    noded -> shell    = XtCreatePopupShell (name,
			topLevelShellWidgetClass, parent,
			shell_args, XtNumber (shell_args));

    noded -> layout   = XtCreateManagedWidget ("layout",
			layoutWidgetClass, noded -> shell,
			layout_args, XtNumber (layout_args));

    noded -> number   = XtCreateManagedWidget ("number",
			labelWidgetClass, noded -> layout,
			number_args, XtNumber (number_args));

    repeater_args [0].value = (XtArgVal) up_bitmap;

    noded -> up       = XtCreateManagedWidget ("up",
			repeaterWidgetClass, noded -> layout,
			repeater_args, XtNumber (repeater_args));

    repeater_args [0].value = (XtArgVal) down_bitmap;

    noded -> down     = XtCreateManagedWidget ("down",
			repeaterWidgetClass, noded -> layout,
			repeater_args, XtNumber (repeater_args));

    noded -> f_name   = XtCreateManagedWidget ("forceName",
			asciiTextWidgetClass, noded -> layout,
			edit_text_args, XtNumber (edit_text_args));

    XtSetArg (button_args [0], XtNlabel,    "Force:");
    XtSetArg (button_args [1], XtNmenuName, "forceMenu");

    noded -> f_button = XtCreateManagedWidget ("forceButton",
			menuButtonWidgetClass, noded -> layout,
			button_args, XtNumber (button_args));

    noded -> f_menu   = XtCreatePopupShell ("forceMenu",
			simpleMenuWidgetClass, noded -> layout,
			NULL, 0);

    noded -> c_name   = XtCreateManagedWidget ("constraintName",
			asciiTextWidgetClass, noded -> layout,
			edit_text_args, XtNumber (edit_text_args));

    XtSetArg (button_args [0], XtNlabel,    "Constraint:");
    XtSetArg (button_args [1], XtNmenuName, "constraintMenu");

    noded -> c_button = XtCreateManagedWidget ("constraintButton",
			menuButtonWidgetClass, noded -> layout,
			button_args, XtNumber (button_args));

    noded -> c_menu   = XtCreatePopupShell ("constraintMenu",
			simpleMenuWidgetClass, noded -> layout,
			NULL, 0);

    noded -> loc_x    = XtCreateManagedWidget ("locationX",
			asciiTextWidgetClass, noded -> layout,
			edit_text_args, XtNumber (edit_text_args));

    noded -> loc_y    = XtCreateManagedWidget ("locationY",
			asciiTextWidgetClass, noded -> layout,
			edit_text_args, XtNumber (edit_text_args));

    noded -> loc_z    = XtCreateManagedWidget ("locationZ",
			asciiTextWidgetClass, noded -> layout,
			edit_text_args, XtNumber (edit_text_args));

    noded -> trans_x  = XtCreateManagedWidget ("translationX",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> trans_y  = XtCreateManagedWidget ("translationY",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> trans_z  = XtCreateManagedWidget ("translationZ",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> rot_x    = XtCreateManagedWidget ("rotationX",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> rot_y    = XtCreateManagedWidget ("rotationY",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> rot_z    = XtCreateManagedWidget ("rotationZ",
			asciiTextWidgetClass, noded -> layout,
			read_text_args, XtNumber (read_text_args));

    noded -> accept   = XtCreateManagedWidget ("accept",
			commandWidgetClass, noded -> layout,
			NULL, 0);

    noded -> dismiss  = XtCreateManagedWidget ("dismiss",
			commandWidgetClass, noded -> layout,
			NULL, 0);

    noded -> delete   = XtCreateManagedWidget ("delete",
			commandWidgetClass, noded -> layout,
			NULL, 0);

    noded -> new      = XtCreateManagedWidget ("new",
			commandWidgetClass, noded -> layout,
			NULL, 0);

    noded -> copy     = XtCreateManagedWidget ("copy",
			commandWidgetClass, noded -> layout,
			NULL, 0);

    noded -> help     = CreateHelpButton (noded -> layout, "help");

    for (i = 0; i < XtNumber (labels); i ++) {
	label_args [0].value = (XtArgVal) labels [i];
	XtCreateManagedWidget (names [i], labelWidgetClass,
		noded -> layout, label_args, XtNumber (label_args));
    }


    XtCreateManagedWidget ("separator1", coreWidgetClass,
			noded -> layout, core_args, XtNumber (core_args));

    XtCreateManagedWidget ("separator2", coreWidgetClass,
			noded -> layout, core_args, XtNumber (core_args));

    XtCreateManagedWidget ("separator3", coreWidgetClass,
			noded -> layout, core_args, XtNumber (core_args));

    XtCreateManagedWidget ("- none -", smeBSBObjectClass,
			noded -> f_menu, NULL, 0);

    XtCreateManagedWidget ("- none -", smeBSBObjectClass,
			noded -> c_menu, NULL, 0);


    AddPostMenuActions (noded -> f_menu);
    AddPostMenuActions (noded -> c_menu);


    /* Create a tab group for the node dialog. */

    group [0]  = noded -> up;
    group [1]  = noded -> down;
    group [2]  = noded -> f_button;
    group [3]  = noded -> f_name;
    group [4]  = noded -> c_button;
    group [5]  = noded -> c_name;
    group [6]  = noded -> loc_x;
    group [7]  = noded -> loc_y;
    group [8]  = noded -> loc_z;
    group [9]  = noded -> help;
    group [10] = noded -> accept;
    group [11] = noded -> dismiss;
    group [12] = noded -> delete;
    group [13] = noded -> new;
    group [14] = noded -> copy;

    XtGetValues (noded -> layout, color_args, XtNumber (color_args));
    CreateTabGroup (noded -> shell, group, XtNumber (group), highlight, True);
    XtRealizeWidget (noded -> shell);
    SetFocus (noded -> up);

    XtSetArg (args [0], XtNwidth, &width);
    XtGetValues (noded -> layout, args, 1);
    XtSetArg (args [0], XtNx, &x);
    XtGetValues (noded -> help, args, 1);
    UpdateHelpMessage (noded -> help, help_message, width - 2 * x);


    /* Add the translations to each widget. */

    AddDeleteWindowProtocol (noded -> shell, "NodeDialogAction()");

    XtOverrideTranslations (noded -> f_name,   text_translations);
    XtOverrideTranslations (noded -> c_name,   text_translations);
    XtOverrideTranslations (noded -> loc_x,    text_translations);
    XtOverrideTranslations (noded -> loc_y,    text_translations);
    XtOverrideTranslations (noded -> loc_z,    text_translations);
    XtOverrideTranslations (noded -> f_button, button_translations);
    XtOverrideTranslations (noded -> c_button, button_translations);
    XtOverrideTranslations (noded -> accept,   command_translations);
    XtOverrideTranslations (noded -> dismiss,  command_translations);
    XtOverrideTranslations (noded -> new,      command_translations);
    XtOverrideTranslations (noded -> copy,     command_translations);
    XtOverrideTranslations (noded -> up,       repeater_translations);
    XtOverrideTranslations (noded -> down,     repeater_translations);
    XtOverrideTranslations (noded -> help,     help_translations);


    /* Add the necessary callbacks. */

    XtAddCallback (noded -> up,      XtNcallback, Up,      (XtPointer) noded);
    XtAddCallback (noded -> down,    XtNcallback, Down,    (XtPointer) noded);
    XtAddCallback (noded -> accept,  XtNcallback, Accept,  (XtPointer) noded);
    XtAddCallback (noded -> dismiss, XtNcallback, Dismiss, (XtPointer) noded);
    XtAddCallback (noded -> delete,  XtNcallback, Delete,  (XtPointer) noded);
    XtAddCallback (noded -> new,     XtNcallback, New,     (XtPointer) noded);
    XtAddCallback (noded -> copy,    XtNcallback, Copy,    (XtPointer) noded);

    XtAddCallback (noded -> f_menu, XtNpopupCallback,
		   UpdateForceMenu, (XtPointer) noded);

    XtAddCallback (noded -> f_menu, XtNpopdownCallback,
		   UpdateForceName, (XtPointer) noded);

    XtAddCallback (noded -> c_menu, XtNpopupCallback,
		   UpdateConstraintMenu, (XtPointer) noded);

    XtAddCallback (noded -> c_menu, XtNpopdownCallback,
		   UpdateConstraintName, (XtPointer) noded);

    return noded;
}


/************************************************************************
 * Function:	NodeDialogPopup						*
 *									*
 * Description:	Pops up the specified node dialog.			*
 ************************************************************************/

void NodeDialogPopup (noded)
    NodeDialog noded;
{
    XtPopup (noded -> shell, XtGrabNone);
}


/************************************************************************
 * Function:	NodeDialogUpdate					*
 *									*
 * Description:	Updates the specified node dialog with the specified	*
 *		trees.							*
 ************************************************************************/

void NodeDialogUpdate (noded, nodes, forces, constraints)
    NodeDialog noded;
    Tree       nodes;
    Tree       forces;
    Tree       constraints;
{
    /* Remember to update the menus if necessary. */

    if (forces != NULL) {
	noded -> forces = forces;
	noded -> new_forces = True;
    }

    if (constraints != NULL) {
	noded -> constraints = constraints;
	noded -> new_constraints = True;
    }


    /* Determine a new active node if necessary. */

    if (nodes == NULL && noded -> active == NULL)
	noded -> active = (Node) TreeMinimum (noded -> nodes);

    if (nodes != NULL && (noded -> active == NULL || noded -> nodes != nodes))
	noded -> active = (Node) TreeMinimum (nodes);

    if (nodes != NULL) {
	noded -> nodes = nodes;
	if (noded -> active != NULL)
	    NodeDialogDisplay (noded, noded -> active);
	else
	    New (NULL, (XtPointer) noded, NULL);
    }
}


/************************************************************************
 * Function:	NodeDialogActive					*
 *									*
 * Description:	Returns the currently displayed (active) node.		*
 ************************************************************************/

Node NodeDialogActive (noded)
    NodeDialog noded;
{
    return noded -> active;
}


/************************************************************************
 * Function:	NodeDialogDisplay					*
 *									*
 * Description:	Displays the specified node.				*
 ************************************************************************/

void NodeDialogDisplay (noded, node)
    NodeDialog noded;
    Node       node;
{
    char buffer [32];
    Node active;


    /* Check if the node exists. */

    noded -> active = node;

    if (noded -> active == NULL)
	return;

    active = noded -> active;
    noded -> new_copy = 0;


    /* Update all of the text entries. */

    SetNumber (noded, active -> number);

    sprintf (buffer, "%g", active -> x);
    SetTextString (noded -> loc_x, buffer);

    sprintf (buffer, "%g", active -> y);
    SetTextString (noded -> loc_y, buffer);

    sprintf (buffer, "%g", active -> z);
    SetTextString (noded -> loc_z, buffer);

    sprintf (buffer, "%g", active -> dx [Tx]);
    SetTextString (noded -> trans_x, buffer);

    sprintf (buffer, "%g", active -> dx [Ty]);
    SetTextString (noded -> trans_y, buffer);

    sprintf (buffer, "%g", active -> dx [Tz]);
    SetTextString (noded -> trans_z, buffer);

    sprintf (buffer, "%g", active -> dx [Rx]);
    SetTextString (noded -> rot_x, buffer);

    sprintf (buffer, "%g", active -> dx [Ry]);
    SetTextString (noded -> rot_y, buffer);

    sprintf (buffer, "%g", active -> dx [Rz]);
    SetTextString (noded -> rot_z, buffer);

    if (active -> force != NULL)
	SetTextString (noded -> f_name, active -> force -> name);
    else
	SetTextString (noded -> f_name, "");

    if (active -> constraint != NULL)
	SetTextString (noded -> c_name, active -> constraint -> name);
    else
	SetTextString (noded -> c_name, "");
}
