/*
 * File: dtb_utils.c
 * CDE Application Builder General Utility Functions
 *
 * This file was generated by dtcodegen, from project PalmSync
 *
 *    ** DO NOT MODIFY BY HAND - ALL MODIFICATIONS WILL BE LOST **
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Frame.h>
#include <Xm/Label.h>
#include <Xm/MessageB.h>
#include <Xm/PanedW.h>
#include <Xm/PushB.h>
#include <Xm/SashP.h>
#include <Xm/RowColumn.h>
#include <Dt/Help.h>
#include <Dt/HelpDialog.h>
#include <Dt/HelpQuickD.h>
#include <Dt/Session.h>
#include <Dt/Dnd.h>
#include "dtb_utils.h"

#ifndef min
#define min(a,b) ((a) < (b)? (a):(b))
#endif

#ifndef max
#define max(a,b) ((a) > (b)? (a):(b))
#endif

#ifndef ABS
#define ABS(x) ((x) >= 0? (x):(-(x)))
#endif

typedef struct
{
    Widget              widget;
    DtDndProtocol       protocol;
    unsigned char       operations;
    Boolean             bufferIsText;
    Boolean             allowDropOnRootWindow;
    Widget              sourceIcon;
    DtbDndDragCallback  callback;
    XtCallbackRec       convertCBRec[2];
    XtCallbackRec       dragToRootCBRec[2];
    XtCallbackRec       dragFinishCBRec[2];
} DtbDragSiteRec, *DtbDragSite;

typedef struct
{
    Widget              widget;
    DtDndProtocol       protocols;
    unsigned char       operations;
    Boolean             textIsBuffer;
    Boolean             dropsOnChildren;
    Boolean             preservePreviousRegistration;
    DtbDndDropCallback  callback;
    XtCallbackRec       animateCBRec[2];
    XtCallbackRec       transferCBRec[2];
} DtbDropSiteRec, *DtbDropSite;

/*
 * This structure keeps track of widget/menu pairs
 */
typedef struct
{
    Widget              widget;
    Widget              menu;
} DtbMenuRefRec, *DtbMenuRef;

/*
 * Private functions used for dynamic centering of objects
 */
static void  center_widget(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
);
static void  uncenter_widget(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
);
static void centering_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
);
/*
 * Static functions used for dynamic aligning of group objects
 */
static Widget get_label_widget(
    Widget	widget
);
static Position get_offset_from_ancestor(
    Widget	ancestor,
    Widget	w
);
static Dimension get_label_width(
    Widget	widget
);
static void get_widest_label(
    WidgetList	list,
    int		count,
    Widget	*child_widget,
    Dimension	*label_width
);
static void get_widest_value(
    WidgetList	list,
    int		count,
    Widget	*child_widget,
    Dimension	*value_width
);
static void get_widget_rect(
    Widget widget,
    XRectangle *rect
);
static void get_greatest_size(
    Widget	*list,
    int		count,
    int		*width,
    int		*height,
    Widget	*tallest,
    Widget	*widest
);
static void get_group_cell_size(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			*cell_width,
    int			*cell_height
);
static void get_group_row_col(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			*rows,
    int			*cols
);
static Widget get_group_child(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			x_pos,
    int			y_pos
);
static void align_children(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
);
static void align_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
);
static void expose_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
);
static void		 free_group_info(
    Widget	widget,
    XtPointer	client_data,
    XtPointer	call_data
);
static void align_rows(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
);
static void align_cols(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
);
static void align_left(
    Widget		parent,
    DtbGroupInfo	*group_info
);
static void align_right(
    Widget		parent,
    DtbGroupInfo	*group_info
);
static void align_labels(
    Widget		parent,
    DtbGroupInfo	*group_info
);
static void align_vcenter(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
);
static void align_top(
    Widget		parent,
    DtbGroupInfo	*group_info
);
static void align_bottom(
    Widget		parent,
    DtbGroupInfo	*group_info
);
static void align_hcenter(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
);

/*
 * Private functions used for finding paths
 */
static int  determine_exe_dir(
    char 	*argv0,
    char 	*buf,
    int 	bufSize
);

static int determine_exe_dir_from_argv(
    char 	*argv0,
    char 	*buf,
    int 	bufSize
);

static int determine_exe_dir_from_path (
    char 	*argv0,
    char 	*buf,
    int 	bufSize
);

static Boolean path_is_executable(
    char 	*path,
    uid_t	euid,
    gid_t 	egid
);

static void dtb_popup_menu(
    Widget	widget,
    XEvent	*event,
    String	*params,
    Cardinal	*num_params
);

static void  dtb_popup_menu_destroyCB(
			Widget		widget,
			XtPointer	clientData,
			XtPointer	callData
);

static int dtb_drag_terminate(DtbDragSite dragSite);

static void dtb_drag_button1_motion_handler(
        Widget          dragInitiator,
        XtPointer       clientData,
        XEvent         *event
);

static void dtb_drag_button2_event_handler(
        Widget          dragInitiator,
        XtPointer       clientData,
        XEvent         *event
);

static int dtb_drag_start(DtbDragSite dragSite, XEvent *event);

static void dtb_drag_convertCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
);

static void dtb_drag_to_rootCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
);

static void dtb_drag_finishCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
);

static void dtb_drop_animateCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
);

static void dtb_drop_transferCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
);


/*
 * Variable for storing client session save callback
 */
static DtbClientSessionSaveCB		dtb_client_session_saveCB = NULL;

/*
 * Variable for storing top level widget
 */
static Widget		dtb_project_toplevel_widget = (Widget)NULL;

/*
 * Variable for storing command used to invoke application
 */
static char				*dtb_save_command_str = (char *)NULL;

/*
 * Variables that keep track of which menus go with which widgets
 */
static DtbMenuRef	popupMenus = NULL;
static int	numPopupMenus = 0;
#include <Dt/Dnd.h>

#define DRAG_THRESHOLD  4
static Boolean  dragInProgress = False;
static int	dragInitialX = -1;
static int	dragInitialY = -1;


/*
 * Directory where the binary for this process whate loaded from
 */
static char				*dtb_exe_dir = (char *)NULL;

/*
 * Application Builder utility funcs
 */
/*
 * Create/load a Pixmap given an XPM or Bitmap files
 * NOTE: this allocates a server Pixmap;  it is the responsibility
 * of the caller to free the Pixmap
 */
int
dtb_cvt_file_to_pixmap(
    String	fileName,
    Widget	widget,
    Pixmap	*pixmapReturnPtr
)
{
#define pixmapReturn (*pixmapReturnPtr)
    Pixmap	pixmap = NULL;
    Screen	*screen = NULL;
    Pixel	fgPixel = 0;
    Pixel	bgPixel = 0;
    char	image_path[MAXPATHLEN+1];
    Boolean	pixmap_found = False;

    /*
     * Get default values
     */
    screen = XtScreenOfObject(widget);
    fgPixel = WhitePixelOfScreen(screen);
    bgPixel = BlackPixelOfScreen(screen);

    /*
     * Get proper colors for widget
     */
    XtVaGetValues(widget,
	XmNforeground, &fgPixel,
	XmNbackground, &bgPixel,
	NULL);

    /*
     * In CDE, XmGetPixmap handles .xpm files, as well.
     */
    if (!pixmap_found)
    {
        pixmap = XmGetPixmap(screen, fileName, fgPixel, bgPixel);
    }
    pixmap_found = ((pixmap != NULL) && (pixmap != XmUNSPECIFIED_PIXMAP));

    if (!pixmap_found)
    {
        sprintf(image_path, "%s/%s", dtb_get_exe_dir(), fileName);
        pixmap = XmGetPixmap(screen, image_path, fgPixel, bgPixel);
    }
    pixmap_found = ((pixmap != NULL) && (pixmap != XmUNSPECIFIED_PIXMAP));

    if (!pixmap_found)
    {
        sprintf(image_path, "%s/bitmaps/%s", dtb_get_exe_dir(), fileName);
        pixmap = XmGetPixmap(screen, image_path, fgPixel, bgPixel);
    }
    pixmap_found = ((pixmap != NULL) && (pixmap != XmUNSPECIFIED_PIXMAP));

    if (!pixmap_found)
    {
	return -1;
    }

    pixmapReturn = pixmap;
    pixmapReturn = pixmap;
    return 0;
#undef pixmapReturn
}


/*
 * Sets both the sensitive and insensitve pixmaps
 */
int
dtb_set_label_from_bitmap_data(
    Widget		widget,
    int			width,
    int			height,
    unsigned char	*bitmapData
)
{
    Display		*display = NULL;
    Screen		*screen = NULL;
    Drawable		window = NULL;
    long		bgPixel = 0;
    long		fgPixel = 0;
    unsigned int	depth = 0;
    Pixmap		labelPixmap = NULL;

    if (   (widget == NULL)
	|| (width < 1)
	|| (height < 1)
	|| (bitmapData == NULL) )
    {
        return -1;
    }

    /*
     * Get a whole slew of information X needs
     */
    {
	Pixel		widgetBg = 0;
	Pixel		widgetFg = 0;
	int		widgetDepth = 0;

        display = XtDisplay(widget);
        screen = XtScreen(widget);
        window = XtWindow(widget);
	if (window == NULL)
	{
	    /* Widget has not been realized, yet */
	    window = RootWindowOfScreen(screen);
	}

	XtVaGetValues(XtIsSubclass(widget, xmGadgetClass)? XtParent(widget) : widget,
		XmNbackground,	&widgetBg,
		XmNforeground,	&widgetFg,
		XmNdepth,	&widgetDepth,
		NULL);
	bgPixel = widgetBg;
	fgPixel = widgetFg;
	depth = widgetDepth;
    }

    /*
     * Create the pixmap
     */
    labelPixmap = XCreatePixmapFromBitmapData(
		display,
		window,
		(char *) bitmapData,
		width, height,
		fgPixel, bgPixel,
		depth);
    if (labelPixmap == NULL)
    {
	return -1;
    }

    dtb_set_label_pixmaps(widget, labelPixmap, NULL);

    return 0;
}


/*
 * Sets the label and insensitive label pixmaps of the widget.  
 *
 * If either (or both) pixmap is NULL, it is ignored.
 */
int
dtb_set_label_pixmaps(
    Widget	widget,
    Pixmap	labelPixmap,
    Pixmap	labelInsensitivePixmap
)
{
    if (   (widget == NULL)
	|| ((labelPixmap == NULL) && (labelInsensitivePixmap == NULL)) )
    {
	return -1;
    }

    /*
     * Set the approriate resources.
     */
    XtVaSetValues(widget, XmNlabelType,	XmPIXMAP, NULL);
    if (labelPixmap != NULL)
    {
	XtVaSetValues(widget, XmNlabelPixmap, labelPixmap, NULL);
    }
    if (labelInsensitivePixmap != NULL)
    {
	XtVaSetValues(widget, XmNlabelInsensitivePixmap, 
				labelInsensitivePixmap, NULL);
    }

    return 0;
}


/*
 * Returns True if the fileName has the extension
 */
Boolean
dtb_file_has_extension(
    String	fileName, 
    String	extension
)
{
    Boolean        hasExt = False;

    if (extension == NULL)
    {
        hasExt = ( (fileName == NULL) || (strlen(fileName) == 0) );
    }   
    else
    {   
        if (fileName == NULL)
            hasExt = False;
        else
        {
            char *dotPtr= strrchr(fileName, '.');
            if (dotPtr == NULL)
                hasExt= False;
            else if (strcmp(dotPtr+1, extension) == 0)
	 	hasExt = True;
        }
    }
    return hasExt;
}


/*
 * Appends the extension to fileBase and attempts to load in
 * the Pixmap 
 */
int
dtb_cvt_filebase_to_pixmap(
    Widget	widget,
    String	fileBase,
    String	extension,
    Pixmap	*pixmap_ptr
)
{
    static char		*fileName = NULL;
    static int		fileNameSize = 0;
    int			rc = 0;
    int			newSize = 0;

    (*pixmap_ptr) = NULL;

    /*
     * Allocate a larger buffer, if necessary
     */
    if ((newSize = (strlen(fileBase) + strlen(extension) + 1)) > fileNameSize)
    {
	char	*newFileName = NULL;
	newFileName = (char*)realloc(fileName, newSize);
	if (newFileName == NULL)
	{
	    return -1;
	}
	fileName = newFileName;
	fileNameSize = newSize;
    }

    /*
     * Actually do the conversion
     */
    *fileName = 0;
    strcat(fileName, fileBase);
    strcat(fileName, extension);
    rc = dtb_cvt_file_to_pixmap(fileName, widget, pixmap_ptr);

    return rc;
}


int
dtb_cvt_image_file_to_pixmap(
		Widget	widget,
		String	fileName,
		Pixmap	*pixmap
)
{
    int		rc = 0;		/* return code */
    Pixmap	tmpPixmap = NULL;
    int		depth;

    if (dtb_file_has_extension(fileName, "pm") ||
 	dtb_file_has_extension(fileName, "xpm") ||	
 	dtb_file_has_extension(fileName, "bm") ||	
 	dtb_file_has_extension(fileName, "xbm"))
    {
    	/* If explicit filename requested, use it directly */
	rc = dtb_cvt_file_to_pixmap(fileName, widget, &tmpPixmap);
    }
    else /* Append extensions to locate best graphic match */
    {
    	XtVaGetValues(XtIsSubclass(widget, xmGadgetClass)? XtParent(widget) : widget, 
		XmNdepth, &depth, NULL);

    	if (depth > 1) /* Look for Color Graphics First */
    	{
    	    rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".pm", &tmpPixmap);
    	    if (rc < 0)
	    	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".xpm", &tmpPixmap);
	    if (rc < 0)
            	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".bm", &tmpPixmap);
            if (rc < 0) 
            	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".xbm", &tmpPixmap); 
    	}
    	else /* Look for Monochrome First */
    	{
            rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".bm", &tmpPixmap);
            if (rc < 0)
            	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".xbm", &tmpPixmap);
            if (rc < 0) 
            	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".pm", &tmpPixmap); 
            if (rc < 0) 
            	rc = dtb_cvt_filebase_to_pixmap(widget, fileName, ".xpm", &tmpPixmap);  
    	}
    }

    if (rc < 0)
    {
	*pixmap = NULL;
	return rc;
    }

    *pixmap = tmpPixmap;
    return 0;
}


/* 
 * Sets the XmNlabel from the image file (either xbitmap or xpixmap format).
 *
 * returns negative on error.
 */
int
dtb_set_label_from_image_file(
		Widget	widget,
		String	fileName
)
{
    int		rc = 0;		/* return code */
    Pixmap	labelPixmap = NULL;
    Pixmap	insensitivePixmap = NULL;

    rc = dtb_cvt_image_file_to_pixmap(widget, fileName, &labelPixmap);
    if (rc < 0)
    {
	return rc;
    }

    insensitivePixmap = dtb_create_greyed_pixmap(widget,labelPixmap);
    rc = dtb_set_label_pixmaps(widget, labelPixmap, insensitivePixmap);
    if (rc < 0)
    {
	return rc;
    }

    return 0;
}


unsigned long
dtb_cvt_resource_from_string(
    Widget		parent,
    String		res_type,
    unsigned int	size_of_type,
    String		res_str_value,
    unsigned long	error_value
)
{
    unsigned long	cvt_value_return = error_value;
    unsigned char	cvt_value1 = 0;
    unsigned short	cvt_value2 = 0;
    unsigned int	cvt_value3 = 0;
    unsigned long	cvt_value4 = 0;
    XtPointer		cvt_value_ptr = NULL;
    int			which_cvt_value = -1;
    XrmValue		source;
    XrmValue		dest;

    if (size_of_type > sizeof(cvt_value_return))
    {
	/* Type we are converting to is too large */
	return cvt_value_return;
    }

    /*
     * Get a data object of the appropriate size
     */
    if (size_of_type == sizeof(cvt_value1))
    {
	which_cvt_value = 1;
	cvt_value_ptr = (XtPointer)&cvt_value1;
    }
    else if (size_of_type == sizeof(cvt_value2))
    {
	which_cvt_value = 2;
	cvt_value_ptr = (XtPointer)&cvt_value2;
    }
    else if (size_of_type == sizeof(cvt_value3))
    {
	which_cvt_value = 3;
	cvt_value_ptr = (XtPointer)&cvt_value3;
    }
    else if (size_of_type == sizeof(cvt_value4))
    {
	which_cvt_value = 4;
	cvt_value_ptr = (XtPointer)&cvt_value4;
    }
    else
    {
	return cvt_value_return;
    }

    /*
     * Actually do the conversion
     */
    source.size = strlen(res_str_value) + 1;
    source.addr = res_str_value;

    dest.size   = size_of_type;
    dest.addr   = (char *)cvt_value_ptr;

    if (XtConvertAndStore(parent, XtRString, &source,
		res_type, &dest) != 0)
    {
	switch (which_cvt_value)
	{
	    case 1:
		cvt_value_return = (unsigned long)cvt_value1;
	    break;

	    case 2:
		cvt_value_return = (unsigned long)cvt_value2;
	    break;

	    case 3:
		cvt_value_return = (unsigned long)cvt_value3;
	    break;

	    case 4:
		cvt_value_return = (unsigned long)cvt_value4;
	    break;
	}
    }

    return cvt_value_return;
}


/*
 * For a given pixmap, create a 50% greyed version.  Most likely this will
 * be used where the source pixmap is the labelPixmap for a widget and an 
 * insensitivePixmap is needed so the widget will look right when it is 
 * "not sensitive" ("greyed out" or "inactive").
 * 
 * NOTE: This routine creates a Pixmap, which is an X server resource.  The
 *       created pixmap must be freed by the caller when it is no longer
 *	 needed.
 */
Pixmap
dtb_create_greyed_pixmap(
    Widget	widget,
    Pixmap	pixmap
)
{
    Display	 *dpy;
    Window	 root;
    Pixmap       insensitive_pixmap;
    Pixel	 background;
    unsigned int width, height, depth, bw;
    int		 x,y;
    XGCValues    gcv;
    XtGCMask     gcm;
    GC           gc;


    dpy = XtDisplayOfObject(widget);

    if(pixmap == XmUNSPECIFIED_PIXMAP || pixmap == (Pixmap)NULL) {
	return((Pixmap)NULL);
    }

    XtVaGetValues(widget,
	XmNbackground, &background,
	NULL);

    /* Get width/height of source pixmap */
    if (!XGetGeometry(dpy,pixmap,&root,&x,&y,&width,&height,&bw,&depth)) {
	    return((Pixmap)NULL);
    }
    gcv.foreground = background;
    gcv.fill_style = FillStippled;
    gcv.stipple = XmGetPixmapByDepth(XtScreenOfObject(widget),
			"50_foreground", 1, 0, 1);
    gcm = GCForeground | GCFillStyle | GCStipple;
    gc = XtGetGC(widget, gcm, &gcv);

    /* Create insensitive pixmap */
    insensitive_pixmap = XCreatePixmap(dpy, pixmap, width, height, depth);
    XCopyArea(dpy, pixmap, insensitive_pixmap, gc, 0, 0, width, height, 0, 0);
    XFillRectangle(dpy, insensitive_pixmap, gc, 0, 0, width, height);

    XtReleaseGC(widget, gc);
    return(insensitive_pixmap);
}


/*
** Routines to save and access the toplevel widget for an application.
** This is useful in dtb_ convenience functions, and also probably by
** developers in routines they provide in their _stubs.c files.
** static Widget dtb_project_toplevel_widget = (Widget) NULL;
*/
void
dtb_save_toplevel_widget(
    Widget	toplevel
)
{
	dtb_project_toplevel_widget = toplevel;
}


Widget
dtb_get_toplevel_widget()
{
	return(dtb_project_toplevel_widget);
}


/*
** Function to turn off traversal on the invisible sash within a
** PanedWindow.  This is primarily used for the PanedWindow within
** a Custom Dialog object.
*/
void
dtb_remove_sash_focus(
    Widget	widget
)
{
    WidgetList	children;
    int		numChildren, i;

    if (widget == NULL || !XtIsSubclass(widget, xmPanedWindowWidgetClass))
	return;

    XtVaGetValues(widget,
	XmNchildren,	&children,
	XmNnumChildren, &numChildren,
	NULL);

    for(i = 0; i < numChildren; i++)
	if (XtIsSubclass(children[i], xmSashWidgetClass))
	    XtVaSetValues(children[i], XmNtraversalOn, False, NULL);
}


/*
 ** Routines to save and access the command used to invoke the application. 
 */
void
dtb_save_command(
    char	*argv0
)
{
    char	exe_dir[MAXPATHLEN+1];
    dtb_save_command_str = argv0; 

    /*
     * Save the path to the executable
     */
    if (determine_exe_dir(argv0, exe_dir, MAXPATHLEN+1) >= 0)
    {
	dtb_exe_dir = (char *)malloc(strlen(exe_dir)+1);
	if (dtb_exe_dir != NULL)
	{
	    strcpy(dtb_exe_dir, exe_dir);
	}
    }
}


char * 
dtb_get_command() 
{
    return(dtb_save_command_str); 
}


/* 
** Generic callback function to be attached as XmNhelpCallback and
** provide support for on-object and Help-key help.  The help text to
** be displayed is provided via a specialized data structure passed in
** as client data.
*/

void
dtb_help_dispatch(
    Widget 	widget,
    XtPointer 	clientData,
    XtPointer 	callData
)
{
    DtbObjectHelpData 	help_data = (DtbObjectHelpData)clientData;
    int             	i;
    Arg             	wargs[10];
    char		buffer[100];
    Widget		back_button;
    static Widget	Quick_help_dialog = (Widget)NULL;
    static Widget	MoreButton;

    /* 
    ** In order to save the more-help info (help volume & location ID) as part
    ** of the quick help dialog's backtrack mechanism, we have to splice the 
    ** volume & ID strings together and save them as the help volume field.
    ** If there isn't supplemental help information, we save a null string.
    **
    ** Checking the status of the more-help info also lets us decide whether
    ** the "More..." button should be enabled on the dialog.
    */
    if( help_data->help_volume     ==0 || *(help_data->help_volume) == NULL ||
	help_data->help_locationID ==0 || *(help_data->help_locationID)== NULL){
		buffer[0] = '\0';
    }
    else {
	sprintf(buffer,"%s/%s",help_data->help_volume,help_data->help_locationID);
    }

    /* 
    ** If this is our first time to post help, create the proper dialog and
    ** set its attributes to suit the current object.  If not, then just
    ** update the attributes.
    **
    ** (You have to be careful about gratuitous SetValues on the dialog because
    ** its internal stack mechanism takes repeated settings as separate items
    ** and updates the stack for each.)
    */
    if(Quick_help_dialog == (Widget)NULL) {
        /* Create shared help dialog */
        i = 0;
	XtSetArg(wargs[i],XmNtitle, "Application Help");            i++;
	XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_DYNAMIC_STRING); i++;
	XtSetArg(wargs[i],DtNstringData,help_data->help_text);      i++;
        XtSetArg(wargs[i],DtNhelpVolume,buffer);		    i++;
	Quick_help_dialog = DtCreateHelpQuickDialog(dtb_get_toplevel_widget(),
		"Help",wargs,i);

	/* 
	** Fetch out the Dialog's More button child and hook the 'more help'
	** handler to its activateCallback.  Set it's current status to
	** indicate whether this object has supplemental help data. 
	*/
	MoreButton = DtHelpQuickDialogGetChild(Quick_help_dialog,
		DtHELP_QUICK_MORE_BUTTON);
	XtManageChild(MoreButton);
	XtAddCallback(MoreButton,XmNactivateCallback,dtb_more_help_dispatch,
		(XtPointer)Quick_help_dialog);
	if(buffer[0] == '\0') XtSetSensitive(MoreButton,False);

	/* 
	** Fetch out the Dialog's Backtrack button child & hook a callback
	** that will control button sensitivity based on the presence of more
	** help data.
	*/
	back_button = DtHelpQuickDialogGetChild(Quick_help_dialog,
		DtHELP_QUICK_BACK_BUTTON);
	XtAddCallback(back_button,XmNactivateCallback,dtb_help_back_hdlr,
		(XtPointer)Quick_help_dialog);
    }
    /* Otherwise the dialog already exists so we just set the attributes. */
    else {
	/* 
	** If we have supplemental help info, enable the more button.
	** Also save this info for later use in the backtrack handler.
	*/
	if(buffer[0] == '\0') {
	    XtSetSensitive(MoreButton,False);
	}
	else {
	    XtSetSensitive(MoreButton,True);
	}

        XtVaSetValues(Quick_help_dialog,
    	    DtNhelpType, DtHELP_TYPE_DYNAMIC_STRING,
            DtNhelpVolume,buffer,
            DtNstringData,help_data->help_text,
	    NULL);
    }

    /* Now display the help dialog */
    XtManageChild(Quick_help_dialog);
}


/*
** This callback is invoked when the user presses "More..." on the
** QuickHelpDialog.  It figures out whether a help volume entry is associated
** with the displayed help text, and if so it brings up a GeneralHelpDialog
** to display the appropriate help volume information.
*/
void
dtb_more_help_dispatch(
    Widget 	widget,
    XtPointer 	clientData,
    XtPointer 	callData
)
{
    int             	i;
    Arg             	wargs[10];
    String		buffer, vol, loc;
    char		*cp;
    static Widget	GeneralHelpDialog = (Widget) NULL;
    Widget		help_dialog = (Widget)clientData;

    /* Fetch the saved volume/locationID information from the dialog widget */
    XtVaGetValues(help_dialog,
	DtNhelpVolume,&buffer,
	NULL);

    /* 
    ** Parse the combined volume/locationID string.  If that fails there
    ** must be no data, so don't bother displaying the GeneralHelpDialog.
    ** (We shouldn't be in this callback routine if that happens, though...)
    */
    if( (cp=strrchr(buffer,'/')) != (char *)NULL) {
	*cp++ = 0;
	vol = buffer;
	loc = cp; 
    }

    if(GeneralHelpDialog == (Widget)NULL) {
	/* Create General Help Dialog */
        i = 0;
	XtSetArg(wargs[i],XmNtitle, "Application Help");        i++;
	XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);      i++;
	XtSetArg(wargs[i],DtNhelpVolume, vol);			i++;
	XtSetArg(wargs[i],DtNlocationId,loc);			i++;

	GeneralHelpDialog = DtCreateHelpDialog(dtb_get_toplevel_widget(),
		"GeneralHelp",wargs,i);
    }
    else {
        i = 0;
        XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);  	i++;
        XtSetArg(wargs[i],DtNhelpVolume,vol);			i++;
        XtSetArg(wargs[i],DtNlocationId,loc);			i++;
        XtSetValues(GeneralHelpDialog,wargs,i);
    }

    /* Now take down the quick help dialog and display the full help one */
    XtManageChild(GeneralHelpDialog);
    XtUnmanageChild(help_dialog);
}


/*
** Callback that is added to the QuickHelpDialog widget's "Backtrack" button
** and is used to control the "More.." button.  At each step in the backtrack,
** this routine checks to see if there is help volume & location info stored
** in the dialog's helpVolume resource.  If so, then the "More..." button is
** enabled.  If not, then it is disabled.
*/
void
dtb_help_back_hdlr(
    Widget 	widget,
    XtPointer 	clientData,
    XtPointer 	callData
)
{
    String		buffer, text;
    char		*cp;
    Widget		more_button;
    Widget		help_dialog = (Widget)clientData;

    /* Fetch the saved volume/locationID information from the dialog widget */
    XtVaGetValues(help_dialog,
	DtNhelpVolume,&buffer,
	DtNstringData,&text,
	NULL);

    /* Get a handle to the "More..." button */
    more_button = DtHelpQuickDialogGetChild(help_dialog,
		DtHELP_QUICK_MORE_BUTTON);
    /* 
    ** Parse the combined volume/locationID string.  Disable the "More..."
    ** button if there isn't any help info, and enable it if there is.
    */
    if( buffer == 0 || (*buffer == NULL) ||
	(cp=strrchr(buffer,'/')) == (char *)NULL) {
		XtSetSensitive(more_button,False);
    }
    else {
		XtSetSensitive(more_button,True);
    }
}


/*
** Utility function used to provide support for on-item help.
** It is typically invoked via a callback on the "On Item" item in the
** main menubar's "Help" menu.
*/
void
dtb_do_onitem_help()
{
    Widget	target;

    /* Call the DtHelp routine that supports interactive on-item help. */
    if(DtHelpReturnSelectedWidgetId(dtb_get_toplevel_widget(),
	(Cursor)NULL,&target) != DtHELP_SELECT_VALID) return;
	
    /*
    ** Starting at the target widget, wander up the widget tree looking
    ** for one that has an XmNhelpCallback, and call the first one we
    ** find.
    */
    while(target != (Widget)NULL) {
	if( XtHasCallbacks(target,XmNhelpCallback) == XtCallbackHasSome) {
	    XtCallCallbacks(target,XmNhelpCallback,(XtPointer)NULL);
	    return;
	}
	else {
	    target = XtParent(target);
	}
    }
    return;
}


/*
** Utility function called to display help volume information.
** It needs the name of the help volume and the location ID (both as
** strings) so it can configure the full help dialog widget properly.
*/
int
dtb_show_help_volume_info(
    char	*volume_name,
    char	*location_id
)
{
    int             	i;
    Arg             	wargs[10];
    static Widget	GeneralHelpDialog = (Widget) NULL;
    
    if(GeneralHelpDialog == (Widget)NULL) {
	/* Create General Help Dialog */
        i = 0;
	XtSetArg(wargs[i],XmNtitle, "Application Help");        i++;
	XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);      i++;
	XtSetArg(wargs[i],DtNhelpVolume, volume_name);		i++;
	XtSetArg(wargs[i],DtNlocationId,location_id);		i++;

	GeneralHelpDialog = DtCreateHelpDialog(dtb_get_toplevel_widget(),
		"GeneralHelp",wargs,i);
    }
    else {
        i = 0;
        XtSetArg(wargs[i],DtNhelpType, DtHELP_TYPE_TOPIC);  	i++;
        XtSetArg(wargs[i],DtNhelpVolume,volume_name);		i++;
        XtSetArg(wargs[i],DtNlocationId,location_id);		i++;
        XtSetValues(GeneralHelpDialog,wargs,i);
    }

    /* Now display the full help dialog */
    XtManageChild(GeneralHelpDialog);

    return(0);
}


/* 
** dtb_call_help_callback()
** Utility routine to call the help callbacks on a target widget.  This
** is predominantly used to display help data on a dialog by having this
** function as the activate callback on the dialog's help button.
*/
void 
dtb_call_help_callback(
    Widget widget,
    XtPointer clientData,
    XtPointer callData
)
{
	Widget target = (Widget)clientData;

	XtCallCallbacks(target,XmNhelpCallback,(XtPointer)NULL);
}


/*
 * dtb_session_save()
 * Callback that is called when the application (top level
 * widget of application) gets a WM_SAVE_YOURSELF ClientMessage
 * This callback will call the client/application's session
 * save callback.
 */
void
dtb_session_save(
    Widget 	widget,
    XtPointer 	clientData,
    XtPointer 	callData
)
{
    int				new_argc,
    				client_argc = 0,
    				new_argc_counter,
    				i;
    char			**new_argv,
    				**client_argv = NULL,
    				*session_file_path,
    				*session_file_name,
    				*app_name = NULL;
    Boolean			status = False;
    DtbClientSessionSaveCB	session_saveCB;

    /*
     * Return if no widget passed in.
     */
    if (!widget)
        return;

    /*
     * Get session file path/name to store application's state
     */
    if (DtSessionSavePath(widget, &session_file_path, &session_file_name) == False)
        return;

    /*
     * Get client session save callback
     */
    session_saveCB = dtb_get_client_session_saveCB();

    /*
     * Call client session save callback
     */
    if (session_saveCB)
        /*
         * client_argv and client_argc are the variables that
         * will contain any extra command line options
         * that need to be used when invoking the application
         * to bring it to the current state.
         */
        status = session_saveCB(widget, session_file_path,
        			&client_argv, &client_argc);

    /*
     * Generate the reinvoking command and add it as the property value
     */

    /*  
     * Fetch command used to invoke application
     */
    app_name = dtb_get_command();

    /*
     * new_argc and new_argc are the variables used to reconstruct
     * the command to re-invoke the application
     */

    /*
     * Start new_argc with:
     *	1	for argv[0], normally the application
     *	client_argc	any extra command line options as
     *		returned from client session save
     *		callback
     */
    new_argc = 1 + client_argc;

    /*
     * If the status returned from session save callback is 'True',
     * the session file was actually used. This means we need to
     * add:
     * 	-session <session file name>
     * to the command saved, which is 2 more strings.
     */
    if (status)
        new_argc += 2;

    /*
     * Allocate vector
     */
    new_argv = (char **)XtMalloc((sizeof(char **) * new_argc));

    /*
     * Set new_argv[0] to be the application name
     */
    new_argc_counter = 0;
    new_argv[new_argc_counter] = app_name;
    new_argc_counter++;

    /*
     * Proceed to copy every command line option from
     * client_argv. Skip -session, if found.
     */
    for (i=0; i < client_argc;)
    {
        if (strcmp(client_argv[i], "-session"))
        {
            new_argv[new_argc_counter] = client_argv[i];
            new_argc_counter++;
        }
        else
        {
            /*
             * Skip "-session"
             * The next increment below will skip the session file.
             */
            ++i;
        }

        ++i;
        
    }

    /*
     * If session file used, add
     *	-session <session file name>
     */
    if (status)
    {
        new_argv[new_argc_counter] = "-session";
        new_argc_counter++;
        new_argv[new_argc_counter] = session_file_name;
    }
    else
    {
        /*
         * otherwise, destroy session file
         */
        (void)unlink(session_file_path);
    }

    /*
     * Set WM_COMMAND property with vector constructed
     */
    XSetCommand(XtDisplay(widget), XtWindow(widget),
    		new_argv, new_argc);

    /*
     * Free argument vector
     */
    XtFree ((char *)new_argv);

    /*
     * CDE Sessioning API states that the path/name
     * strings have to be free'd by the application.
     */
    XtFree ((char *)session_file_path);
    XtFree ((char *)session_file_name);
}


/*
 * dtb_get_client_session_saveCB()
 */
DtbClientSessionSaveCB
dtb_get_client_session_saveCB()
{
    return(dtb_client_session_saveCB);

}


/*
 * This function will center all the passed form's children.
 * The type of centering depends on what 'type' is.
 */
void
dtb_children_center(
    Widget		form,
    DTB_CENTERING_TYPES	type
)
{
    WidgetList		children_list;
    int			i, 
			num_children;

    if (!form || (type == DTB_CENTER_NONE))
	return;

    /*
     * Get children list
     */
    XtVaGetValues(form,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    /*
     * Center all children
     */
    for (i=0; i < num_children; ++i)
    {
	dtb_center(children_list[i], type);
    }
}


/*
 * This function 'uncenters' the children of the passed
 * form widget.
 */
void
dtb_children_uncenter(
    Widget		form,
    DTB_CENTERING_TYPES	type
)
{
    WidgetList		children_list;
    int			i;
    int			num_children;

    if (!form || (type == DTB_CENTER_NONE))
	return;

    /*
     * Get children list
     */
    XtVaGetValues(form,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    /*
     * Center all children
     */
    for (i=0; i < num_children; ++i)
    {
	dtb_uncenter(children_list[i], type);
    }
}


/*
 * This function centers the passed widget.
 * This is done by setting the proper offsets.
 * Dynamic centering is accomplished by attaching an event handler
 * which detect resizes and recomputes and sets the appropriate offset.
 */
void
dtb_center(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
)
{
    if (!form_child || (type == DTB_CENTER_NONE))
	return;

   center_widget(form_child, type);

   XtAddEventHandler(form_child,
            StructureNotifyMask, False,
            centering_handler, (XtPointer)type);
}


/*
 * This function 'uncenters' the passed widget.
 * This involves resetting the attachment offsets
 * and removing the resize event handler.
 */
void
dtb_uncenter(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
)
{
    if (!form_child || (type == DTB_CENTER_NONE))
	return;

   uncenter_widget(form_child, type);

   XtRemoveEventHandler(form_child,
            StructureNotifyMask, False,
            centering_handler, (XtPointer)type);
}


/*
 * This function centers the passed widget.
 * This is done by making the appropriate offset equal 
 * to the negative half of it's width/height (depending
 * on whether horizontal or vertical centering was chosen.
 */
static void 
center_widget(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
)
{
    Widget		parent;
    Dimension		width = 0,
			height = 0;
    int			center_offset;
    unsigned char	left_attach = XmATTACH_NONE,
			top_attach = XmATTACH_NONE;

    if (!form_child || !XtIsManaged(form_child) || !XtIsRealized(form_child))
	return;

    parent = XtParent(form_child);

    if (!parent || !XtIsSubclass(parent, xmFormWidgetClass))
	return;

    XtVaGetValues(form_child,
		XmNwidth, &width,
		XmNheight, &height,
		XmNleftAttachment, &left_attach,
		XmNtopAttachment, &top_attach,
                NULL);

    switch (type) {
	case DTB_CENTER_POSITION_VERT:

            if (left_attach != XmATTACH_POSITION)
	        return;

            center_offset = -(width/2);

            XtVaSetValues(form_child,
		XmNleftOffset, center_offset,
		NULL);

	break;

	case DTB_CENTER_POSITION_HORIZ:

            if (top_attach != XmATTACH_POSITION)
	        return;

            center_offset = -(height/2);

            XtVaSetValues(form_child,
		XmNtopOffset, center_offset,
		NULL);
	break;

	case DTB_CENTER_POSITION_BOTH:
	{
            int		left_offset,
			top_offset;

            if ((left_attach != XmATTACH_POSITION) &&
	        (top_attach != XmATTACH_POSITION))
	        return;
	    
	    left_offset = -(width/2);
	    top_offset = -(height/2);

            XtVaSetValues(form_child,
		XmNleftOffset, left_offset,
		XmNtopOffset, top_offset,
		NULL);
	}
	break;
    }
}


/*
 * This function 'uncenters' the passed widget.
 * It merely resets the offsets of the top/left attachments to 0.
 */
static void 
uncenter_widget(
    Widget		form_child,
    DTB_CENTERING_TYPES	type
)
{
    Widget		parent;
    unsigned char	left_attach = XmATTACH_NONE,
			top_attach = XmATTACH_NONE;

    if (!form_child || !XtIsManaged(form_child) || !XtIsRealized(form_child))
	return;

    parent = XtParent(form_child);

    if (!parent || !XtIsSubclass(parent, xmFormWidgetClass))
	return;

    XtVaGetValues(form_child,
		XmNleftAttachment, &left_attach,
		XmNtopAttachment, &top_attach,
                NULL);

    switch (type) {
	case DTB_CENTER_POSITION_VERT:

            if (left_attach != XmATTACH_POSITION)
	        return;

            XtVaSetValues(form_child,
		XmNleftOffset, 0,
		NULL);

	break;

	case DTB_CENTER_POSITION_HORIZ:

            if (top_attach != XmATTACH_POSITION)
	        return;

            XtVaSetValues(form_child,
		XmNtopOffset, 0,
		NULL);
	break;

	case DTB_CENTER_POSITION_BOTH:

            if ((left_attach != XmATTACH_POSITION) &&
	        (top_attach != XmATTACH_POSITION))
	        return;

            XtVaSetValues(form_child,
		XmNleftOffset, 0,
		XmNtopOffset, 0,
		NULL);
	break;
    }
}


/*
 * Event handler to center a widget
 * The type of centering needed is passed in as client_data
 */
static void
centering_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
)
{
    XConfigureEvent	*xcon = &event->xconfigure;
    Widget		resized_child;
    DTB_CENTERING_TYPES	type = (DTB_CENTERING_TYPES)client_data;


    if ((event->type != ConfigureNotify) && (event->type != MapNotify))
        return;

    resized_child = XtWindowToWidget(XtDisplay(widget), xcon->window);

    if (!resized_child)
	return;

    center_widget(resized_child, type);
}


/*
 * Given a widget, return it's label widget.
 */
static Widget
get_label_widget(
    Widget	widget
)
{
    WidgetList	children_list;
    Widget	label_widget = NULL;
    int		num_children = 0;
    char	*subobj_name = NULL,
		*label_name = NULL;
    char	*underscore_ptr = NULL;

    if (XtIsSubclass(widget, xmLabelWidgetClass))  {
	return(widget);
    }

    subobj_name = XtName(widget);
    label_name = (char *)XtMalloc(1 + strlen(subobj_name) + strlen("_label") + 5);
    label_name[0] = '*';
    strcpy(label_name+1, subobj_name);
    if ((underscore_ptr = strrchr(label_name, '_')) != NULL)
    {
	strcpy(underscore_ptr, "_label");
        label_widget = XtNameToWidget(widget, label_name);
    }
    if (label_widget == NULL)
    {
	strcpy(label_name+1, subobj_name);
	strcat(label_name, "_label");
        label_widget = XtNameToWidget(widget, label_name);
    }

    XtFree((char *)label_name);

    if (label_widget)
	return(label_widget);

    /*
     * How to look for 1st child of group object ??
     * How do we know if 'widget' is a group object ??
     * For now, just check if it is a form
     */
    if (XtIsSubclass(widget, xmFormWidgetClass) || 
        XtIsSubclass(widget, xmFrameWidgetClass))
        XtVaGetValues(widget,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children > 0)
        return(get_label_widget(children_list[0]));

    return (NULL);
}


static Position
get_offset_from_ancestor(
    Widget	ancestor,
    Widget	w
)
{
    Widget	cur = w;
    Widget	cur_parent = NULL;
    Position	offset = 0;

    if (!ancestor || !w || (w == ancestor))
	return (0);

    XtVaGetValues(cur, XmNx, &offset, NULL);

    cur_parent = XtParent(cur);

    while (cur_parent != ancestor)
    {
        Position	tmp_offset = 0;

	cur = cur_parent;
        XtVaGetValues(cur, XmNx, &tmp_offset, NULL);
	
	offset += tmp_offset;
        cur_parent = XtParent(cur);
    }

    return (offset);
}


static Dimension
get_label_width(
    Widget	widget
)
{
    Widget	lbl_widget = NULL;
    Dimension	lbl_width = 0;

    lbl_widget = get_label_widget(widget);

    if (lbl_widget)
    {
	Position	offset = 0;

        XtVaGetValues(lbl_widget,
            XmNwidth, &lbl_width,
            NULL);

	offset = get_offset_from_ancestor(widget, lbl_widget);

	lbl_width += (Dimension)offset;
    }
    
    return (lbl_width);
}


static void
get_widest_label(
    WidgetList	list,
    int		count,
    Widget	*child_widget,
    Dimension	*label_width
)
{
    Widget	cur_widest = NULL;
    Dimension	cur_width = 0;
    int		i;

    for (i = 0; i < count; ++i)
    {
	Dimension	tmp;

	tmp = get_label_width(list[i]);

	if (tmp > cur_width)
	{
	    cur_width = tmp;
	    cur_widest = list[i];
	}
    }
    
    *child_widget = cur_widest;
    *label_width = cur_width;
}


static void
get_widest_value(
    WidgetList	list,
    int		count,
    Widget	*child_widget,
    Dimension	*value_width
)
{
    Widget	cur_widest = NULL;
    Dimension	cur_width = 0;
    int		i;

    for (i = 0; i < count; ++i)
    {
	Dimension	tmp, label_width, obj_width = 0;

	label_width = get_label_width(list[i]);
	XtVaGetValues(list[i], XmNwidth, &obj_width, NULL);

	tmp = obj_width - label_width;

	if (tmp > cur_width)
	{
	    cur_width = tmp;
	    cur_widest = list[i];
	}
    }
    
    *child_widget = cur_widest;
    *value_width = cur_width;
}


static void
get_widget_rect(
    Widget widget,
    XRectangle *rect
)
{
    if (!rect)
	return;

    XtVaGetValues(widget,
        XtNwidth,       (XtArgVal)&(rect->width),
        XtNheight,      (XtArgVal)&(rect->height),
        XtNx,           (XtArgVal)&(rect->x),
        XtNy,           (XtArgVal)&(rect->y),
        NULL);
}


static void
get_greatest_size(
    Widget	*list,
    int		count,
    int		*width,
    int		*height,
    Widget	*tallest,
    Widget	*widest
)
{
    XRectangle  w_rect;
    int         i;
    int		previous_width, previous_height;

    if (!list || (count < 0))
	return;

    get_widget_rect(list[0], &w_rect);

    *width = w_rect.width;
    previous_width = *width;

    *height = w_rect.height;
    previous_height = *height;

    if (tallest != NULL)
        *tallest = list[0];

    if (widest != NULL)
    	*widest = list[0];

    for (i=0; i < count; i++)
    {
        get_widget_rect(list[i], &w_rect);

        *width = max((int) w_rect.width, (int) *width); 
	if (widest != NULL && *width > previous_width)
		*widest = list[i];

        *height = max((int) w_rect.height, (int)*height);
	if (tallest != NULL && *height > previous_height)
		*tallest = list[i];
    }
}


static void
get_group_cell_size(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			*cell_width,
    int			*cell_height
)
{
    WidgetList	children_list = NULL;
    int		num_children = 0;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    get_greatest_size(children_list, num_children,
		cell_width, cell_height,
		(Widget *)NULL, (Widget *)NULL);
}


static void
get_group_row_col(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			*rows,
    int			*cols
)
{
    WidgetList	children_list = NULL;
    int		num_rows,
		num_cols,
    		num_children;

    if (!parent || !group_info)
    {
	*rows = *cols = -1;

	return;
    }

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    num_rows = group_info->num_rows;
    num_cols = group_info->num_cols;

    if ((num_rows <= 0) && (num_cols <= 0))
    {
	*rows = *cols = -1;

	return;
    }

    if (num_cols <= 0)
        num_cols = (num_children/num_rows) + ((num_children % num_rows) ? 1 : 0);

    if (num_rows <= 0)
        num_rows = (num_children/num_cols) + ((num_children % num_cols) ? 1 : 0);

    *rows = num_rows;
    *cols = num_cols;
}


static Widget
get_group_child(
    Widget		parent,
    DtbGroupInfo	*group_info,
    int			x_pos,
    int			y_pos
)
{
    DTB_GROUP_TYPES	group_type;
    WidgetList		children_list = NULL;
    Widget		ret_child = NULL;
    int			num_children = 0,
			num_rows,
			num_columns,
    			i = -1;

    if (!parent || !group_info || 
       (x_pos < 0) || (y_pos < 0))
	return (NULL);

    group_type = group_info->group_type;
    num_rows = group_info->num_rows;
    num_columns = group_info->num_cols;

    /*
     * Get number of children
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return (NULL);

    switch (group_type)
    {
	case DTB_GROUP_NONE:
	break;

	case DTB_GROUP_ROWS:
	    /*
	     * num_rows = 1
	     * y_pos is ignored
	     */
	    i = x_pos;
	break;

	case DTB_GROUP_COLUMNS:
	    /*
	     * num_columns = 1
	     * x_pos is ignored
	     */
	    i = y_pos;
	break;

	case DTB_GROUP_ROWSCOLUMNS:
	    if (!num_rows && !num_columns)
		break;

	    if (num_rows > 0)
	    {
		/*
		 * ROWFIRST
		 */
		if (y_pos < num_rows)
		    i = (x_pos * num_rows) + y_pos;
	    }
	    else
	    {
		/*
		 * COLFIRST
		 */
		if (x_pos < num_columns)
		    i = x_pos + (y_pos * num_columns);
	    }
	break;

	default:
	break;
    }

    if ((i >= 0) && (i < num_children))
    {
        ret_child = children_list[i];
    }

    return (ret_child);
}


void
dtb_children_align(
    Widget		parent,
    DTB_GROUP_TYPES	group_type,
    DTB_ALIGN_TYPES	row_align,
    DTB_ALIGN_TYPES	col_align,
    int			margin,
    int			num_rows,
    int			num_cols,
    int			hoffset,
    int			voffset
)
{
    DtbGroupInfo		*group_info;

    switch (group_type)
    {
	case DTB_GROUP_COLUMNS:
	    num_rows = 0;
	    num_cols = 1;
	break;

	case DTB_GROUP_ROWS:
	    num_rows = 1;
	    num_cols = 0;
	break;

    }

    group_info = (DtbGroupInfo *)XtMalloc(sizeof(DtbGroupInfo));

    group_info->group_type = group_type;
    group_info->row_align = row_align;
    group_info->col_align = col_align;
    group_info->margin = margin;
    group_info->num_rows = num_rows;
    group_info->num_cols = num_cols;
    group_info->hoffset = hoffset;
    group_info->voffset = voffset;
    group_info->ref_widget = NULL;

    align_children(parent, group_info, True);

    /*
     * Register expose handler
     * Some group objects depend on it's members' sizes for their layout.
     * Unfortunately, some group members have invalid sizes prior to
     * XtRealize(), so the group layout has to be recalculated after the
     * group is realized or exposed in this case, since there is no realize
     * callback.
     */
    switch(group_info->group_type)
    {
	case DTB_GROUP_NONE:
	break;

	case DTB_GROUP_ROWS:
	    if (group_info->row_align == DTB_ALIGN_HCENTER)
		XtAddEventHandler(parent,
	                ExposureMask, False,
	                expose_handler, (XtPointer)group_info);
	break;

	case DTB_GROUP_COLUMNS:
	    if ((group_info->col_align == DTB_ALIGN_LABELS) || 
		(group_info->col_align == DTB_ALIGN_VCENTER))
		XtAddEventHandler(parent,
	                ExposureMask, False,
	                expose_handler, (XtPointer)group_info);
	break;

	case DTB_GROUP_ROWSCOLUMNS:
	    if ((group_info->row_align == DTB_ALIGN_HCENTER) ||
	        (group_info->col_align == DTB_ALIGN_LABELS) || 
		(group_info->col_align == DTB_ALIGN_VCENTER))
		XtAddEventHandler(parent,
	                ExposureMask, False,
	                expose_handler, (XtPointer)group_info);
	break;

    }

    XtAddCallback(parent, XtNdestroyCallback, 
		free_group_info, (XtPointer)group_info);
}


static void
align_children(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
)
{
    if (!parent || !group_info)
	return;

    switch(group_info->group_type)
    {
        case DTB_GROUP_NONE:
        break;

        case DTB_GROUP_ROWS:
	    align_rows(parent, group_info, init);
	    align_left(parent, group_info);
        break;

        case DTB_GROUP_COLUMNS:
	    align_cols(parent, group_info, init);
	    align_top(parent, group_info);
        break;

        case DTB_GROUP_ROWSCOLUMNS:
	    align_rows(parent, group_info, init);
	    align_cols(parent, group_info, init);
        break;

    }

}


static void
align_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
)
{
    DtbGroupInfo	*group_info = (DtbGroupInfo *)client_data;
    WidgetList	children_list;
    int		num_children = 0;
    Boolean	relayout_all = False;


    /*
     * Get children list
     */
    XtVaGetValues(widget,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;

    XtRemoveEventHandler(widget,
                SubstructureNotifyMask, False,
                align_handler, (XtPointer)client_data);

    if (event->type == ConfigureNotify) {
        XConfigureEvent	*xcon = &event->xconfigure;
	Widget		resized_child;

	if (xcon->window != xcon->event)
	{
            resized_child = XtWindowToWidget(XtDisplay(widget), xcon->window);

            switch(group_info->group_type)
            {
                case DTB_GROUP_NONE:
                break;

                case DTB_GROUP_ROWS:
                    if (group_info->row_align == DTB_ALIGN_HCENTER)
                        relayout_all = True;
                break;

                case DTB_GROUP_COLUMNS:
                    if ((group_info->col_align == DTB_ALIGN_LABELS) ||
                        (group_info->col_align == DTB_ALIGN_VCENTER))
                            relayout_all = True;
                break;

                case DTB_GROUP_ROWSCOLUMNS:
                    if ((group_info->row_align == DTB_ALIGN_HCENTER) ||
                        (group_info->col_align == DTB_ALIGN_LABELS) ||
                        (group_info->col_align == DTB_ALIGN_VCENTER))
                        relayout_all = True;
                break;

            }
        }
    }

    /*
     * Relayout when new widgets are created
     */
    if (event->type == CreateNotify) {
        XCreateWindowEvent	*xcreate = &event->xcreatewindow;

	relayout_all = True;
    }

    /*
     * Relayout when widgets are destroyed
     */
    if (event->type == DestroyNotify) {
        XDestroyWindowEvent	*xdestroy = &event->xdestroywindow;
	Widget			destroyed_child;

        destroyed_child = XtWindowToWidget(XtDisplay(widget), 
			xdestroy->window);

	relayout_all = True;
    }

    if (relayout_all)
    {
        align_children(widget, group_info, False);
    }

    XtAddEventHandler(widget,
                SubstructureNotifyMask, False,
                align_handler, (XtPointer)client_data);
}


static void
expose_handler(
    Widget	widget,
    XtPointer	client_data,
    XEvent	*event,
    Boolean	*cont_dispatch
)
{
    DtbGroupInfo	*group_info = (DtbGroupInfo *)client_data;
    WidgetList	children_list;
    int		num_children = 0;
    Boolean	relayout_all = False,
		register_align_handler = False;


    if (event->type != Expose) 
	return;
    
    if (!group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(widget,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;

    XtRemoveEventHandler(widget,
                ExposureMask, False,
                expose_handler, (XtPointer)client_data);

    switch(group_info->group_type)
    {
        case DTB_GROUP_NONE:
        break;

        case DTB_GROUP_ROWS:
        if (group_info->row_align == DTB_ALIGN_HCENTER)
        {
            relayout_all = True;

            register_align_handler = True;
        }
        break;

        case DTB_GROUP_COLUMNS:
        if ((group_info->col_align == DTB_ALIGN_LABELS) ||
            (group_info->col_align == DTB_ALIGN_VCENTER))
        {
            relayout_all = True;

            register_align_handler = True;
        }
        break;

        case DTB_GROUP_ROWSCOLUMNS:
        if ((group_info->row_align == DTB_ALIGN_HCENTER) ||
            (group_info->col_align == DTB_ALIGN_LABELS) ||
            (group_info->col_align == DTB_ALIGN_VCENTER))
        {
            relayout_all = True;

            register_align_handler = True;
        }
        break;
    }

    if (relayout_all)
    {
        align_children(widget, group_info, False);
    }

    if (register_align_handler)
    {
	/*
	 * Register align handler to relayout group if/when
	 * any of it's members resize
	 */
	XtAddEventHandler(widget,
            SubstructureNotifyMask, False,
            align_handler, (XtPointer)group_info);
    }
}


static void		
free_group_info(
    Widget	widget,
    XtPointer	client_data,
    XtPointer	call_data
)
{
    DtbGroupInfo	*group_info = (DtbGroupInfo *)client_data;;

    XtFree((char *)group_info);
}


static void
align_rows(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
)
{
    if (!parent || !group_info || (group_info->group_type == DTB_GROUP_COLUMNS))
	return;

    switch (group_info->row_align)
    {
        case DTB_ALIGN_TOP:
            align_top(parent, group_info);
        break;

        case DTB_ALIGN_HCENTER:
            align_hcenter(parent, group_info, init);
        break;

        case DTB_ALIGN_BOTTOM:
            align_bottom(parent, group_info);
        break;

        default:
        break;
    }
}


static void
align_cols(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
)
{
    if (!parent || !group_info || (group_info->group_type == DTB_GROUP_ROWS))
	return;

    switch (group_info->col_align)
    {
        case DTB_ALIGN_LEFT:
            align_left(parent, group_info);
        break;

        case DTB_ALIGN_LABELS:
            align_labels(parent, group_info);
        break;

        case DTB_ALIGN_VCENTER:
            align_vcenter(parent, group_info, init);
        break;

        case DTB_ALIGN_RIGHT:
            align_right(parent, group_info);
        break;

        default:
        break;
    }
}


static void
align_left(
    Widget		parent,
    DtbGroupInfo	*group_info
)
{
    WidgetList	children_list;
    Widget	child, 
		previous_child;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_width,
		cell_height,
		i,
		j;
  
    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    for (j = 0; j < num_rows; j++)
    {
        for (i = 0; i < num_columns; i++)
        {
            Arg		args[12];
	    int		n = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    if ((i == 0) && (j == 0))
	    {
	        XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNleftOffset, 0);			n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    if (j == 0)
	    {
	        int			offset = group_info->hoffset;
		DTB_GROUP_TYPES		group_type = group_info->group_type;

	        previous_child = get_group_child(parent, group_info, i-1, j);

		if (!previous_child)
		    continue;

                if (group_type == DTB_GROUP_ROWSCOLUMNS)
		{
		    Dimension	width = 0;

		    XtVaGetValues(previous_child, XmNwidth, &width, NULL);
		    offset += (cell_width - (int)(width));
		}

	        XtSetArg(args[n], XmNleftAttachment, 
				XmATTACH_WIDGET);			n++;
	        XtSetArg(args[n], XmNleftWidget, previous_child);	n++;
	        XtSetArg(args[n], XmNleftOffset, offset);		n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    previous_child = get_group_child(parent, group_info, i, j-1);

	    if (previous_child)
	    {
	        XtSetArg(args[n], XmNleftAttachment, 
				    XmATTACH_OPPOSITE_WIDGET);		n++;
	        XtSetArg(args[n], XmNleftWidget, previous_child);	n++;
	        XtSetArg(args[n], XmNleftOffset, 0);			n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);
	    }
        }
    }
}


static void
align_right(
    Widget		parent,
    DtbGroupInfo	*group_info
)
{
    WidgetList	children_list;
    Widget	child, 
		previous_child;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_width,
		cell_height,
	        offset,
		i,
		j;

    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    for (j = 0; j < num_rows; j++)
    {
        for (i = 0; i < num_columns; i++)
        {
            Arg		args[12];
	    int		n = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    if ((i == 0) && (j == 0))
	    {
		Dimension	width = 0;

		XtVaGetValues(child, XmNwidth, &width, NULL);

		offset = (cell_width - width);

	        XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNleftOffset, offset);		n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    if (j == 0)
	    {
	        previous_child = get_group_child(parent, group_info, i-1, j);

		if (!previous_child)
		    continue;

	        offset = group_info->hoffset;

                if (group_info->group_type == DTB_GROUP_ROWSCOLUMNS)
		{
		    Dimension	width = 0;

		    XtVaGetValues(child, XmNwidth, &width, NULL);
		    offset += (cell_width - width);
		}

	        XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET);	n++;
	        XtSetArg(args[n], XmNleftWidget, previous_child);	n++;
	        XtSetArg(args[n], XmNleftOffset, offset);		n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    previous_child = get_group_child(parent, group_info, i, j-1);

	    if (previous_child)
	    {
	        XtSetArg(args[n], XmNrightAttachment, 
				XmATTACH_OPPOSITE_WIDGET);		n++;
	        XtSetArg(args[n], XmNrightWidget, previous_child);	n++;
	        XtSetArg(args[n], XmNrightOffset, 0);			n++;
		XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);
	    }
        }
    }
}


static void
align_labels(
    Widget		parent,
    DtbGroupInfo	*group_info
)
{
    WidgetList	children_list = NULL,
		one_col;
    Widget	previous_child = NULL,
		child,
		previous_ref_widget = NULL;
    Dimension	ref_lbl_width = 0,
		max_label_width = 0,
		max_value_width = 0;
    int		num_children = 0,
		num_rows,
		num_columns,
		cell_width,
		cell_height,
		offset,
		i,
		j;

    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);

    get_widest_label(children_list, num_children, &child, &max_label_width);
    get_widest_value(children_list, num_children, &child, &max_value_width);

    if (cell_width < (int)(max_label_width + max_value_width))
        cell_width = (int)(max_label_width + max_value_width);

    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    if (num_rows > 0)
	one_col = (WidgetList)XtMalloc(num_rows * sizeof(WidgetList));

    for (i = 0; i < num_columns; i++)
    {
	Widget		ref_widget;
	Dimension	ref_width;
        Arg		args[12];
	int		n = 0;

        for (j = 0; j < num_rows; j++)
	    one_col[j] = get_group_child(parent, group_info, i, j);

	get_widest_label(one_col, num_rows, &ref_widget, &ref_width);

	if (!ref_widget)
	    continue;

	if (previous_ref_widget)
	    offset = (i * (group_info->hoffset + cell_width));
	else
	    offset = 0;

	XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);	n++;
	XtSetArg(args[n], XmNleftOffset, offset);		n++;
        XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	XtSetValues(ref_widget, args, n);

        for (j = 0; j < num_rows; j++)
        {
	    child = get_group_child(parent, group_info, i, j);

	    if (!child || (child == ref_widget))
		continue;

	    offset = (i * (group_info->hoffset + cell_width));
	    offset += (int)(ref_width - get_label_width(child));

	    n = 0;
	    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);	n++;
	    XtSetArg(args[n], XmNleftOffset, offset);			n++;
	    XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;

	    XtSetValues(child, args, n);
        }

	previous_ref_widget = ref_widget;
    }

    if (num_rows > 0)
        XtFree((char*)one_col);
}


static void
align_vcenter(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
)
{
    WidgetList	children_list;
    Widget	child;
    DTB_GROUP_TYPES group_type;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_width,
		cell_height,
		group_width,
		offset,
		gridline,
		i,
		j;

    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
	 
    get_group_row_col(parent, group_info, &num_rows, &num_columns);
	      
    offset = group_info->hoffset;
		   
    group_type = group_info->group_type;

    if (group_type == DTB_GROUP_ROWSCOLUMNS)
    {
        group_width = (num_columns * cell_width) + ((num_columns-1) * offset);
    }

    for (i = 0; i < num_columns; i++)
    {
        if (group_type == DTB_GROUP_ROWSCOLUMNS)
	    gridline = (((i * (cell_width + offset)) + (cell_width/2)) * 100)/group_width;
	else
	    gridline = 50;

        for (j = 0; j < num_rows; j++)
        {
            Arg		args[12];
	    int		n = 0;
	    Dimension	width = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    XtVaGetValues(child, XmNwidth, &width, NULL);

	    if (init)
	    {
		int	offset = 0;

		if (!XtIsSubclass(child, compositeWidgetClass))
		{
	            offset = (cell_width - (int)width)/2;

                    if (group_type == DTB_GROUP_ROWSCOLUMNS)
	                offset += (i * (cell_width + group_info->hoffset));
		}

	        XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNleftOffset, offset);		n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;
	    }
	    else
	    {
	        XtSetArg(args[n], XmNleftAttachment, 
				XmATTACH_POSITION);			n++;
	        XtSetArg(args[n], XmNleftPosition, gridline);		n++;
	        XtSetArg(args[n], XmNleftOffset, (int)(-(width/2)));	n++;
		XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE);	n++;
	    }

	    XtSetValues(child, args, n);
        }
    }
}


static void
align_top(
    Widget		parent,
    DtbGroupInfo	*group_info
)
{
    WidgetList	children_list;
    Widget	previous_child = NULL,
		child;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_width,
		cell_height,
		i,
		j;

    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    for (j = 0; j < num_rows; j++)
    {
        for (i = 0; i < num_columns; i++)
        {
	    Arg args[12];
	    int n = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    if ((i == 0) && (j == 0))
	    {
	        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNtopOffset, 0);			n++;
	        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    if (i == 0)
	    {
	        previous_child = get_group_child(parent, group_info, 0, j-1);

	        if (previous_child)
	        {
                    DTB_GROUP_TYPES	group_type = group_info->group_type;
	            int			offset = group_info->voffset;
    
                    if (group_type == DTB_GROUP_ROWSCOLUMNS)
		    {
			Dimension	height = 0;

			XtVaGetValues(previous_child, XmNheight, &height, NULL);

		        offset += (cell_height - (int)(height));
		    }

	            XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);	n++;
	            XtSetArg(args[n], XmNtopWidget, previous_child);		n++;
	            XtSetArg(args[n], XmNtopOffset, offset);			n++;
	            XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;

	            XtSetValues(child, args, n);
	        }
		continue;
	    }

	    previous_child = get_group_child(parent, group_info, i-1, j);

	    if (previous_child)
	    {
	        XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET);	n++;
	        XtSetArg(args[n], XmNtopWidget, previous_child);		n++;
	        XtSetArg(args[n], XmNtopOffset, 0);				n++;
	        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);		n++;

	        XtSetValues(child, args, n);
	    }

        }
    }
}


static void
align_bottom(
    Widget		parent,
    DtbGroupInfo	*group_info
)
{
    WidgetList	children_list;
    Widget	child, 
		previous_child;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_height,
		cell_width,
		offset,
		i,
		j;
  
    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    for (j = 0; j < num_rows; j++)
    {
        for (i = 0; i < num_columns; i++)
        {
            Arg		args[12];
	    int		n = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    if ((i == 0) && (j == 0))
	    {
		Dimension	height = 0;

		XtVaGetValues(child, XmNheight, &height, NULL);

		offset = cell_height - height;

	        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNtopOffset, offset);		n++;
	        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);

		continue;
	    }

	    if (i == 0)
	    {
	        previous_child = get_group_child(parent, group_info, 0, j-1);

	        if (previous_child)
	        {
		    Dimension	height = 0;

		    XtVaGetValues(child, XmNheight, &height, NULL);

	            offset = group_info->voffset;

                    if (group_info->group_type == DTB_GROUP_ROWSCOLUMNS)
		        offset += (cell_height - height);

	            XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET);	n++;
	            XtSetArg(args[n], XmNtopWidget, previous_child);		n++;
	            XtSetArg(args[n], XmNtopOffset, offset);			n++;
	            XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;

	            XtSetValues(child, args, n);
	        }
		continue;
	    }

	    previous_child = get_group_child(parent, group_info, i-1, j);

	    if (child && previous_child)
	    {
	        XtSetArg(args[n], XmNbottomAttachment, 
				XmATTACH_OPPOSITE_WIDGET);		n++;
	        XtSetArg(args[n], XmNbottomWidget, previous_child);	n++;
	        XtSetArg(args[n], XmNbottomOffset, 0);			n++;
	        XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE);	n++;

	        XtSetValues(child, args, n);
	    }
        }
    }
}


static void
align_hcenter(
    Widget		parent,
    DtbGroupInfo	*group_info,
    Boolean		init
)
{
    WidgetList	children_list = NULL;
    Widget	child;
    DTB_GROUP_TYPES group_type;
    int		num_children = 0,
		num_columns,
		num_rows,
		cell_width,
		cell_height,
		group_height,
		offset,
		gridline,
		i,
		j;
  
    if (!parent || !group_info)
	return;

    /*
     * Get children list
     */
    XtVaGetValues(parent,
            XmNnumChildren, &num_children,
            XmNchildren, &children_list,
            NULL);

    if (num_children <= 0)
	return;
    
    group_type = group_info->group_type;

    get_group_cell_size(parent, group_info, &cell_width, &cell_height);
    get_group_row_col(parent, group_info, &num_rows, &num_columns);

    offset = group_info->voffset;

    if (group_type == DTB_GROUP_ROWSCOLUMNS)
    {
        group_height = (num_rows * cell_height) + ((num_rows-1) * offset);
    }

    for (j = 0; j < num_rows; j++)
    {
        if (group_type == DTB_GROUP_ROWSCOLUMNS)
	    gridline = 
		(((j * (cell_height + offset)) + (cell_height/2)) * 100)/group_height;
	else
	    gridline = 50;

        for (i = 0; i < num_columns; i++)
        {
            Arg		args[12];
	    int		n = 0;
	    Dimension	height = 0;

	    child = get_group_child(parent, group_info, i, j);

	    if (!child)
		continue;

	    XtVaGetValues(child, XmNheight, &height, NULL);

	    if (init)
	    {
		int	init_offset = 0;

                if (!XtIsSubclass(child, compositeWidgetClass))
		{
		    init_offset = (cell_height - (int)height)/2;

		    if (group_type == DTB_GROUP_ROWSCOLUMNS)
		    init_offset += (j * (cell_height + group_info->voffset));
		}

	        XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);	n++;
	        XtSetArg(args[n], XmNtopOffset, init_offset);		n++;
	        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;
	    }
	    else
	    {
	        XtSetArg(args[n], XmNtopAttachment, 
				XmATTACH_POSITION);			n++;
	        XtSetArg(args[n], XmNtopPosition, gridline);		n++;
	        XtSetArg(args[n], XmNtopOffset, (int)(-(height/2)));	n++;
	        XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE);	n++;
	    }

	    XtSetValues(child, args, n);
        }
    }
}


/*
 * Returns the directory that the executable for this process was loaded 
 * from.
 */
String
dtb_get_exe_dir(void)
{
    return dtb_exe_dir;
}


/*
 * Determines the directory the executable for this process was loaded from.
 */
static int 
determine_exe_dir(
    char 	*argv0,
    char 	*buf,
    int 	bufSize
)
{
    Boolean	foundDir= False;

    if ((buf == NULL) || (bufSize < 1))
    {
	return -1;
    }
    
    if (determine_exe_dir_from_argv(argv0, buf, bufSize) >= 0)
    {
	foundDir = True;
    }

    if (!foundDir)
    {
	if (determine_exe_dir_from_path(argv0, buf, bufSize) >= 0)
	{
	    foundDir = True;
	}
    }

    /*
     * Convert relative path to absolute, so that directory changes will
     * not affect us.
     */
    if (foundDir && (buf[0] != '/'))
    {
	char	cwd[MAXPATHLEN+1];
	char	*env_pwd = NULL;
	char	*path_prefix = NULL;
	char	abs_exe_dir[MAXPATHLEN+1];

	if (getcwd(cwd, MAXPATHLEN+1) != NULL)
	{
	    path_prefix = cwd;
	}
	else
	{
	    env_pwd = getenv("PWD");
	    if (env_pwd != NULL)
	    {
		path_prefix = env_pwd;
	    }
	}

	if (path_prefix != NULL)
	{
	    strcpy(abs_exe_dir, path_prefix);
	    if (strcmp(buf, ".") != 0)
	    {
		strcat(abs_exe_dir, "/");
		strcat(abs_exe_dir, buf);
	    }
	    strcpy(buf, abs_exe_dir);
	}
    }

    return foundDir? 0:-1;
}


/*
 *  Looks for absolute path in arv[0].
 */
static int
determine_exe_dir_from_argv(
    char 	*argv0,
    char 	*buf,
    int 	bufSize
)
{
    int		i= 0;
    Boolean	foundit= False;

    for (i= strlen(argv0)-1; (i >= 0) && (argv0[i] != '/'); --i)
    {
    }

    if (i >= 0)
    {
	int	maxStringSize= min(i, bufSize);
	strncpy(buf, argv0, maxStringSize);
	buf[min(maxStringSize, bufSize-1)]= 0;
	foundit = True;
    }

    return foundit? 0:-1;
}


/*
 * Assumes: bufSize > 0
 */
static int
determine_exe_dir_from_path (
    char 	*argv0,
    char 	*buf,
    int 	bufSize
)
{
    Boolean	foundDir= False;
    Boolean	moreDirs= True;
    char	*szExeName= argv0;
    int		iExeNameLen= strlen(szExeName);
    char	*szTemp= NULL;
    char	szPathVar[MAXPATHLEN+1];
    int		iPathVarLen= 0;
    char	szCurrentPath[MAXPATHLEN+1];
    int		iCurrentPathLen= 0;
    int		iCurrentPathStart= 0;
    int		i = 0;
    uid_t	euid= geteuid();
    uid_t	egid= getegid();

    szTemp= getenv("PATH");
    if (szTemp == NULL)
    {
	moreDirs= False;
    }
    else
    {
	strncpy(szPathVar, szTemp, MAXPATHLEN);
	szPathVar[MAXPATHLEN]= 0;
	iPathVarLen= strlen(szPathVar);
    }

    while ((!foundDir) && (moreDirs))
    {
	/* find the current directory name */
	for (i= iCurrentPathStart; (i < iPathVarLen) && (szPathVar[i] != ':'); 
	    )
    	{
	    ++i;
	}
	iCurrentPathLen= i - iCurrentPathStart;
	if ((iCurrentPathLen + iExeNameLen + 2) > MAXPATHLEN)
	{
	    iCurrentPathLen= MAXPATHLEN - (iExeNameLen + 2);
	}

	/* create a possible path to the executable */
	strncpy(szCurrentPath, &szPathVar[iCurrentPathStart], iCurrentPathLen);
	szCurrentPath[iCurrentPathLen]= 0;
	strcat(szCurrentPath, "/");
	strcat(szCurrentPath, szExeName);

	/* see if the executable exists (and we can execute it) */
	if (path_is_executable(szCurrentPath, euid, egid))
	{
	    foundDir= True;
	}

	/* skip past the current directory name */
	if (!foundDir)
	{
	    iCurrentPathStart+= iCurrentPathLen;
	    while (   (iCurrentPathStart < iPathVarLen) 
		   && (szPathVar[iCurrentPathStart] != ':') )
	    {
		++iCurrentPathStart;	/* find : */
	    }
	    if (iCurrentPathStart < iPathVarLen) 
	    {
		++iCurrentPathStart;	/* skip : */
	    }
	    if (iCurrentPathStart >= iPathVarLen)
	    {
		moreDirs= False;
	    }
	}
    } /* while !foundDir */

    if (foundDir)
    {
	szCurrentPath[iCurrentPathLen]= 0;
	strncpy(buf, szCurrentPath, bufSize);
	buf[bufSize-1]= 0;
    }
    return foundDir? 0:-1;
}


/*
 * returns False is path does not exist or is not executable
 */
static Boolean
path_is_executable(
    char 	*path,
    uid_t	euid,
    gid_t 	egid
)
{
    Boolean	bExecutable= False;
    struct stat	sStat;

    if (stat(path, &sStat) == 0)
    {
	Boolean	bDetermined= False;

	if (!bDetermined)
	{
	    if (!S_ISREG(sStat.st_mode))
	    {
		/* not a regular file */
		bDetermined= True;
		bExecutable= False;
	    }
	}

	if (!bDetermined)
	{
	    if (   (euid == 0) 
	        && (   ((sStat.st_mode & S_IXOTH) != 0)
		    || ((sStat.st_mode & S_IXGRP) != 0)
		    || ((sStat.st_mode & S_IXUSR) != 0) )
	       )
	    {
		bDetermined= True;
		bExecutable= True;
	    }
	}

	if (!bDetermined)
	{
	    if (   (((sStat.st_mode & S_IXOTH) != 0)    )
		|| (((sStat.st_mode & S_IXGRP) != 0) && (sStat.st_gid == egid))
		|| (((sStat.st_mode & S_IXUSR) != 0) && (sStat.st_gid == euid))
	       )
	    {
		bDetermined= True;
	        bExecutable= True;
	    }
	}
    } /* if stat */

    return bExecutable;
}


/*
 * Registers a popup menu to be brought by button three on the parent
 */
int 
dtb_popup_menu_register(Widget popupMenu, Widget parent)
{
    static XtTranslations	popupMenuTrans = NULL;
    static XtActionsRec		menuActions[] =
    {
	{"DtbPopupMenu", (XtActionProc)dtb_popup_menu }
    };
    int		i = 0;
    Boolean	foundEntry = False;

    if (popupMenuTrans == NULL)
    {
	XtAppContext	appContext = 
		XtWidgetToApplicationContext(dtb_get_toplevel_widget());
	XtAppAddActions(appContext, menuActions, XtNumber(menuActions));
	popupMenuTrans = XtParseTranslationTable(
				"<Btn3Down>: DtbPopupMenu()");

    }
    XtOverrideTranslations(parent, popupMenuTrans);

    /*
     * Save the reference from this widget to the menu
     */
    /* see if an entry already exists */
    for (i = 0; i < numPopupMenus; ++i)
    {
	if (   (popupMenus[i].widget == parent)
	    || (popupMenus[i].menu == popupMenu) )
	{
	    foundEntry = True;
	    break;
	}
    }
    /* look for an empty slot */
    if (!foundEntry)
    {
	/* look for an empty slot */
	for (i = 0; i < numPopupMenus; ++i)
	{
	    if (popupMenus[i].widget == NULL)
	    {
		foundEntry = True;
		break;
	    }
	}
    }
    /* make a new slot, if necessary */
    if (!foundEntry)
    {
	/* It's not in the list - add it */
	DtbMenuRef newPopupMenus = (DtbMenuRef)
		realloc(popupMenus, sizeof(DtbMenuRefRec)*(numPopupMenus+1));
	if (newPopupMenus != NULL)
	{
	    popupMenus = newPopupMenus;
	    ++numPopupMenus;
	    i = numPopupMenus-1;
	    foundEntry = True;
	}
    }

    /* we have a slot; fill it in */
    if (foundEntry)
    {
        popupMenus[i].widget = parent;
        popupMenus[i].menu = popupMenu;
	XtAddCallback(popupMenus[i].widget,
	    XmNdestroyCallback, dtb_popup_menu_destroyCB, (XtPointer)NULL);
	XtAddCallback(popupMenus[i].menu,
	    XmNdestroyCallback, dtb_popup_menu_destroyCB, (XtPointer)NULL);
    }

    return 0;
}


static void
dtb_popup_menu(
    Widget	widget,
    XEvent	*event,
    String	*params,
    Cardinal	*num_params
)
{
    int		i = 0;
    Widget	menu = NULL;

    if (event->type == ButtonPress)
    {
        for (i = 0 ; i < numPopupMenus; ++i)
        {
	    if (popupMenus[i].widget == widget)
	    {
		menu = popupMenus[i].menu;
	    }
	}
    }

    if (menu != NULL)
    {
	XmMenuPosition(menu, (XButtonPressedEvent*)event);
	XtManageChild(menu);
    }
}


/*
 * This keeps the list of popup menus up-to-date, if widgets are destroyed
 */
static void 
dtb_popup_menu_destroyCB(
			Widget		widget,
			XtPointer	clientData,
			XtPointer	callData
)
{
    int		i = 0;
    for (i = 0; i < numPopupMenus; ++i)
    {
	if (   (popupMenus[i].widget == widget) 
	    || (popupMenus[i].menu == widget) )
	{
	    popupMenus[i].widget = NULL;
	    popupMenus[i].menu = NULL;
	    break;
	}
    }
}


/*
 * Returns non-negative if successful.
 */
int
dtb_drag_site_register(
                        Widget 			widget,
			DtbDndDragCallback	callback,
			DtDndProtocol		protocol,
			unsigned char		operations,
			Boolean			bufferIsText,
			Boolean			allowDropOnRootWindow,
			Pixmap			cursor,
			Pixmap			cursorMask,
			DtbDragSiteHandle	*dragSiteHandleOut
)
{
    DtbDragSite dragSite = (DtbDragSite)XtCalloc(1,sizeof(DtbDragSiteRec));
    Widget	sourceIcon = NULL;

    if (dragSite != NULL)
    {
        dragSite->widget = widget;
    	dragSite->protocol = protocol;
    	dragSite->operations = operations;
	dragSite->bufferIsText = bufferIsText;
	dragSite->allowDropOnRootWindow = allowDropOnRootWindow;
	if ((cursor != NULL) && (cursorMask != NULL))
	{
	    dragSite->sourceIcon = 
			DtDndCreateSourceIcon(widget, cursor, cursorMask);
	}
    	dragSite->callback = callback;
        dragSite->convertCBRec[0].callback = dtb_drag_convertCB;
        dragSite->dragToRootCBRec[0].callback = dtb_drag_to_rootCB;
        dragSite->dragFinishCBRec[0].callback = dtb_drag_finishCB;
    
        XtAddEventHandler(widget, Button1MotionMask, False,
            (XtEventHandler)dtb_drag_button1_motion_handler, 
	    (XtPointer)dragSite);

        XtAddEventHandler(widget, ButtonPressMask, False,
            (XtEventHandler)dtb_drag_button2_event_handler,
            (XtPointer)dragSite);
    }
    
    /*
     * Pass back a handle, so that this can be freed, later. Unregistering
     * drag sites is not currently implemented, but this gives the ability
     * to provide this functionality in the future.
     */
    if (dragSiteHandleOut != NULL)
    {
        *dragSiteHandleOut = (DtbDragSiteHandle)dragSite;
    }
    return 0;
}


int
dtb_drop_site_register(
                        Widget                  widget,
                        DtbDndDropCallback      callback,
                        DtDndProtocol		protocols,
                        unsigned char           operations,
                        Boolean                 dropsOnChildren,
                        Boolean                 preservePreviousRegistration,
                        DtbDropSiteHandle       *dropSiteHandleOut
)
{
    DtbDropSite dropSite = (DtbDropSite)NULL;

    if (   (callback != NULL)
	&& ((dropSite = (DtbDropSite)XtCalloc(1,sizeof(DtbDropSiteRec))) 
								!= NULL) 
       )
    {
	DtbDndDropRegisterInfoRec	regInfo;

	/* initialize the data */
        dropSite->widget = widget;
        dropSite->callback = callback;
        dropSite->protocols = protocols;
        dropSite->operations = operations;
        dropSite->dropsOnChildren = dropsOnChildren;
        dropSite->preservePreviousRegistration = preservePreviousRegistration;
        dropSite->animateCBRec[0].callback = dtb_drop_animateCB;
        dropSite->animateCBRec[0].closure = (XtPointer)dropSite;
        dropSite->transferCBRec[0].callback = dtb_drop_transferCB;
        dropSite->transferCBRec[0].closure = (XtPointer)dropSite;

        /* Let the client modify the drop site initialization */
        regInfo.protocols = dropSite->protocols;
        regInfo.operations = dropSite->operations;
        regInfo.textIsBuffer = dropSite->textIsBuffer;
        regInfo.preservePreviousRegistration = 
				dropSite->preservePreviousRegistration;
        regInfo.respondToDropsOnChildren = dropSite->dropsOnChildren;
        dropSite->callback(widget, DTB_DND_REGISTER, &regInfo, NULL, NULL);

        /* actually register it! */
        DtDndVaDropRegister(
	    widget, regInfo.protocols, regInfo.operations,
	    dropSite->transferCBRec,
	    DtNregisterChildren, regInfo.respondToDropsOnChildren,
	    DtNtextIsBuffer, regInfo.textIsBuffer,
#ifdef DtNpreserveRegistration
	    DtNpreserveRegistration, regInfo.preservePreviousRegistration,
#endif
	    DtNdropAnimateCallback, dropSite->animateCBRec,
	    NULL);
    }

    if (dropSiteHandleOut != NULL)
    {
	*dropSiteHandleOut = (DtbDropSiteHandle)dropSite;
    }

    return ((dropSite == NULL)? -1:0);
}


static int
dtb_drag_terminate(DtbDragSite dragSite)
{
    dragInProgress = False;
    dragInitialX = -1;
    dragInitialY = -1;
    dragSite->convertCBRec[0].closure = NULL;
    dragSite->dragToRootCBRec[0].closure = NULL;
    dragSite->dragFinishCBRec[0].closure = NULL;
    return 0;
}


/*
 * dragMotionHandler
 *
 * Determine if the pointer has moved beyond the drag threshold while button 1
 * was being held down.
 */
static void
dtb_drag_button1_motion_handler(
        Widget          dragInitiator,
        XtPointer       clientData,
        XEvent         *event
)
{
    int             	diffX, diffY;
    DtbDragSite		dragSite = (DtbDragSite)clientData;

    if (!dragInProgress) {
        /*
         * If the drag is just starting, set initial button down coords
         */
        if (dragInitialX == -1 && dragInitialY == -1) {
                dragInitialX = event->xmotion.x;
                dragInitialY = event->xmotion.y;
        }

        /*
         * Find out how far pointer has moved since button press
         */
        diffX = dragInitialX - event->xmotion.x;
        diffY = dragInitialY - event->xmotion.y;
        
        if ((ABS(diffX) >= DRAG_THRESHOLD) ||
            (ABS(diffY) >= DRAG_THRESHOLD)) 
	{
		dtb_drag_start(dragSite, event);
                dragInitialX = -1;
                dragInitialY = -1;
        }
    }
}


/*
 * Starts a drag if Button2 is pressed (if the system is configured for
 * Button2 transfers).
 */
static void
dtb_drag_button2_event_handler(
        Widget          dragInitiator,
        XtPointer       clientData,
        XEvent         *event
)
{
    if (   (event->type == ButtonPress)
        && (event->xbutton.button == 2))
    {
        Boolean         enableBtn1Transfer = (Boolean)0;
        Boolean         mouseButton2IsTransfer = False;

        /*
         * We need to determine whether mouse button 2 is adjust or
         * transfer. Although the resource value is of type Boolean, it
         * can actually have integer values. This code is from page 52
         * of _Common Desktop Environment: Programmer's Guide_.
         */
        XtVaGetValues((Widget)XmGetDisplay(dragInitiator),
                "enableBtn1Transfer", &enableBtn1Transfer,
                NULL);
        mouseButton2IsTransfer = (enableBtn1Transfer != 1);

        if (mouseButton2IsTransfer)
        {
            DtbDragSite         dragSite = (DtbDragSite)clientData;

            /* A mouse-down event has occurred on Button2, and Button2 is
             * configured to be transfer.
             */
            dtb_drag_start(dragSite, event);
        }
    }
}


static int
dtb_drag_start(DtbDragSite dragSite, XEvent *event)
{
    DtbDndDragStartInfoRec	dragStart;
    Arg				args[3];
    int				n = 0;

    dragInProgress = True;

    dragSite->convertCBRec[0].closure = (XtPointer)dragSite;
    dragSite->dragToRootCBRec[0].closure = (XtPointer)dragSite;
    dragSite->dragFinishCBRec[0].closure = (XtPointer)dragSite;

    /*
     * Call the client callback to update values for drag start
     */
    memset((void*)&dragStart, 0, sizeof(DtbDndDragStartInfoRec));
    dragStart.protocol= dragSite->protocol;
    dragStart.operations = dragSite->operations;
    dragStart.cursor = dragSite->sourceIcon;
    dragStart.bufferIsText = dragSite->bufferIsText;
    dragStart.allowDropOnRootWindow = dragSite->allowDropOnRootWindow;
    dragStart.numItems = 1;
    if (dragSite->callback != NULL)
    {
        dragSite->callback(DTB_DND_DRAG_START, &dragStart, NULL,NULL,NULL,NULL);
    }

    n = 0;
    if (dragStart.cursor != NULL)
    {
	XtSetArg(args[n], DtNsourceIcon, dragStart.cursor); ++n;
    }
    if (   (dragStart.bufferIsText) 
	&& (dragStart.protocol == DtDND_BUFFER_TRANSFER) )
    {
	XtSetArg(args[n], DtNbufferIsText, True); ++n;
    }
    if (dragStart.allowDropOnRootWindow)
    {
	XtSetArg(args[n], DtNdropOnRootCallback, (XtPointer)dragSite->dragToRootCBRec); ++n;
    }
    if (DtDndDragStart(dragSite->widget, event, 
	dragStart.protocol,
	dragStart.numItems,
	dragStart.operations,
	dragSite->convertCBRec, dragSite->dragFinishCBRec,
	args, n) == NULL)
    {
	/* drag start failed */
	dtb_drag_terminate(dragSite);
	return -1;
    }
    return 0;
}


static void
dtb_drag_convertCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
)
{
    DtbDragSite			dragSite = (DtbDragSite)clientData;
    DtDndConvertCallback	convert = (DtDndConvertCallback)callData;
    DtDndContext		*dragData = convert->dragData;
    int				i = 0;

    switch (convert->reason)
    {
	case DtCR_DND_CONVERT_DATA:
	    dragSite->callback(DTB_DND_CONVERT,NULL,convert,NULL,NULL,NULL);
	break;

	case DtCR_DND_CONVERT_DELETE:
	    dragSite->callback(DTB_DND_DELETE,NULL,NULL,NULL,convert,NULL);
	break;
    }
}


static void
dtb_drag_to_rootCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
)
{
    DtbDragSite			dragSite = (DtbDragSite)clientData;
    DtbDndDroppedOnRootWindowInfoRec	dropInfo;
    memset(&dropInfo, 0, sizeof(DtbDndDroppedOnRootWindowInfoRec));

    dropInfo.droppedOnRootWindow = True;
    dragSite->callback(DTB_DND_DROPPED_ON_ROOT_WINDOW,
			NULL,NULL,&dropInfo,NULL,NULL);
}


static void
dtb_drag_finishCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
)
{
    DtbDragSite			dragSite = (DtbDragSite)clientData;
    DtDndDragFinishCallback	finish = (DtDndDragFinishCallback)callData;

    dragSite->callback(DTB_DND_FINISH,NULL,NULL,NULL,NULL,finish);
    dtb_drag_terminate((DtbDragSite)clientData);
}


static void
dtb_drop_animateCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
)
{
    DtbDropSite		dropSite = (DtbDropSite)clientData;
    DtDndDropAnimateCallback 	animateInfo = 
					(DtDndDropAnimateCallback)callData;
    dropSite->callback(
	dropSite->widget, DTB_DND_ANIMATE, NULL, NULL, animateInfo);
}


static void
dtb_drop_transferCB(
			Widget		dragContext,
			XtPointer	clientData,
			XtPointer	callData
)
{
    DtbDropSite			dropSite = (DtbDropSite)clientData;
    DtDndTransferCallback	transferInfo = (DtDndTransferCallback)callData;

    dropSite->callback(
	dropSite->widget, DTB_DND_RECEIVE_DATA, NULL, transferInfo, NULL);
}


