/*
 * $XConsortium: utils.c,v 1.17 91/10/09 10:39:44 dave Exp $
 *
 * Copyright 1989 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <X11/Intrinsic.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>

#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Dialog.h>

#include <stdio.h>

#include "editresP.h"

static WidgetResources * ParseResources();
static int CompareResourceEntries();
static void FreeResources(), AddResource();
static WNode * FindWidgetFromWindowGivenNode();

void CreateResourceBox();

extern void PopupCentered(), PerformTreeToFileDump();

/*	Function Name: ShowMessage(w, str)
 *	Description: shows the message to the user.
 *	Arguments: w - a label widget to show the message in.
 *                 str - the string to show.
 *	Returns: none.
 */

void
SetMessage(w, str)
Widget w;
char * str;
{
    Arg args[1];

    XtSetArg(args[0], XtNlabel, str);
    XtSetValues(w, args, ONE);
}

/*	Function Name: GetAllStrings
 *	Description: Returns a list of strings that have been borken up by
 *                   the character specified.
 *	Arguments: in - the string to parse.
 *                 sep - the separator character.
 *                 out - the strings to send out.
 *                 num - the number of strings in out.
 *	Returns: none
 */

void
GetAllStrings(in, sep, out, num)
char *in, sep, ***out;
int * num;
{
    int size, i;
    char * ptr;

    if (*in == sep)		/* jump over first char if it is the sep. */
	in++;

    /*
     * count the number of strings.
     */

    for (*num = 1, ptr = in; (ptr = index(ptr, sep)) != NULL; (*num)++)
	ptr++;

/*
 * Create Enough space for pointers and string.
 */

    size = (sizeof(char *) * *num) + (sizeof(char) * (strlen(in) + 1));
    *out = (char **) XtMalloc( (Cardinal) size);

    ptr = (char *) (*out + *num);
    strcpy(ptr, in);

/*
 * Change all `sep' characters to '\0' and stuff the pointer into
 * the next pointer slot.
 */

    i = 1;
    (*out)[0] = ptr;
    while (TRUE) {
	if ((ptr = index(ptr, sep)) == NULL)
	    break;

	*ptr++ = '\0';
	(*out)[i++] = ptr;
    }

/*
 * If last string is empty then strip it off.
 */

    if ( *((*out)[i - 1]) == '\0' )
	(*num)--;
}

/*	Function Name: AddString
 *	Description: Mallocs and strcats the string onto the end of
 *                   the given string.
 *	Arguments: str - string to add on to.
 *                 add - string to add.
 *	Returns: none.
 */

void
AddString(str, add)
char ** str, *add;
{
    int len_str, len_add;
    char * ptr;

    len_str = ((*str) ? strlen(*str) : 0);
    len_add = strlen(add);

    *str = XtRealloc(*str, sizeof(char) * (len_str + len_add + 1));
    ptr = *str + len_str;
    strcpy(ptr, add);
}
 
/*	Function Name: FindNode
 *	Description: Finds a node give the top node, and a node id number.
 *	Arguments: top_node - the top node.
 *                 id - the node id.
 *	Returns: node.
 */

WNode *
FindNode(top_node, ids, number)
WNode *top_node;
unsigned long * ids;
Cardinal number;
{
    int i, j;
    WNode *node;

    if (top_node == NULL)
	return(NULL);

    if (ids[0] != top_node->id)
	return(NULL);

    for (node = top_node, i = 1 ; i < number; i++) {
	Boolean found_it = FALSE;

	for (j = 0; j < node->num_children; j++) {
	    if (node->children[j]->id == ids[i]) {
		node = node->children[j];
		found_it = TRUE;
		break;
	    }
	}
	if (!found_it)
	    return(NULL);
    }	    
    return(node);
}

/*	Function Name: FindWidgetFromWindow
 *	Description: finds a widget in the current tree given its window id.
 *	Arguments: tree_info - information about this tree.
 *                 win - window to search for.
 *	Returns: node - the node corrosponding to this widget.
 */

WNode * 
FindWidgetFromWindow(tree_info, win)
TreeInfo * tree_info;
Window win;
{
    if (tree_info == NULL)
	return(NULL);

    return(FindWidgetFromWindowGivenNode(tree_info->top_node, win));
}

/*	Function Name: FindWidgetFromWindowGivenNode
 *	Description: finds a widget in the current tree given its window id.
 *	Arguments: node - current node.
 *                 win - window to search for.
 *	Returns: node - the node corrosponding to this widget.
 */

static WNode *
FindWidgetFromWindowGivenNode(node, win)
WNode * node;
Window win;
{
    int i;
    WNode * ret_node;

    if (node->window == win)
	return(node);

    for (i = 0; i < node->num_children; i++) {
	ret_node = FindWidgetFromWindowGivenNode(node->children[i], win);
	if (ret_node != NULL)
	    return(ret_node);
    }
    return(NULL);
}

/*	Function Name: HandleXErrors
 *	Description: Handles error codes from the server.
 *	Arguments: display - the display.
 *                 error - error information.
 *	Returns: none.
 */

/* ARGSUSED */
int
HandleXErrors(display, error)
Display * display;
XErrorEvent * error;
{
    if (error->serial != global_serial_num) {
	(*global_old_error_handler) (display, error);
	return(0);
    }

    if (error->error_code == BadWindow)
	global_error_code = NO_WINDOW;    
    else {
	if (XmuPrintDefaultErrorMessage(display, error, stderr) != 0)
	    exit(1);
    }
    return(0);
}

/*	Function Name: _DumpTreeToFile
 *	Description: Dumps the widget tree to a file
 *	Arguments: w - a random widget in the application on the
 *                     currently active display
 *                 tree_ptr - pointer to the widget tree info.
 *                 filename - name of the file.
 *	Returns: none.
 */

/* ARGSUSED */

void
_DumpTreeToFile(w, tree_ptr, filename)
Widget w;
XtPointer tree_ptr;
char * filename;
{
    TreeInfo * tree_info = (TreeInfo *) tree_ptr;
    FILE * fp; 

    if (tree_info == NULL) {
	SetMessage(global_screen_data.info_label,
		   "No widget Tree is avaliable.");
	return;
    }

    if ( (fp = fopen(filename, "w")) == NULL ) {
	char buf[BUFSIZ];

	sprintf(buf, "Unable to open the file `%s' for writing.", filename);
	SetMessage(global_screen_data.info_label, buf);
	return;
    }

    PerformTreeToFileDump(tree_info->top_node, 0, fp);
    fclose(fp);
}

/************************************************************
 * 
 * The file dialog boxes are handled with this code.
 *
 * It automatically calls the function specified when the
 * user selects okay, or hits <CR>.
 *
 * A translation is required in the app-defaults file.
 *
 ************************************************************/

/*	Function Name: _PopupFileDialog
 *	Description: Puts up a dialog box to get the filename.
 *	Arguments: str - message.
 *                 default_value - the default value of the filename;
 *                 func - function to call when filename has been entered.
 *                 data - generic data to pass to func.
 *	Returns: none
 */

static XContext file_dialog_context = None;

typedef struct _FileDialogInfo {
    void (*func)();
    XtPointer data;
} FileDialogInfo;

void
_PopupFileDialog(w, str, default_value, func, data)
Widget w;
String str, default_value;
void (*func)();
XtPointer data;
{
    FileDialogInfo * file_info;
    Widget shell, dialog;
    Arg args[2];
    Cardinal num_args;
    void _PopdownFileDialog();

    if (file_dialog_context == None)
	file_dialog_context = XUniqueContext();

    shell = XtCreatePopupShell("fileDialog", transientShellWidgetClass, w,
			       NULL, ZERO);

    num_args = 0;
    XtSetArg(args[num_args], XtNlabel, str); num_args++;
    XtSetArg(args[num_args], XtNvalue, default_value); num_args++;
    dialog = XtCreateManagedWidget("dialog", dialogWidgetClass, 
				   shell, args, num_args);

    file_info = XtNew(FileDialogInfo);

    file_info->func = func;
    file_info->data = data;

    if  (XSaveContext(XtDisplay(dialog), (Window) dialog, file_dialog_context, 
		      (caddr_t) file_info) != 0) {
	SetMessage(global_screen_data.info_label,
	    "Error while trying to save Context\nAborting file dialog popup.");
	XtDestroyWidget(shell);
	return;
    }

    XawDialogAddButton(dialog, "okay", _PopdownFileDialog, (XtPointer) TRUE);
    XawDialogAddButton(dialog, "cancel", _PopdownFileDialog,(XtPointer) FALSE);

    PopupCentered(NULL, shell, XtGrabNone);
}

/*	Function Name: PopupCentered
 *	Description: Pops up the window specified under the location passed
 *                   in the event, or under the cursor.
 *	Arguments: event - the event that we should use.
 *                 w - widget to popup.
 *                 mode - mode to pop it up in.
 *	Returns: none
 */

void
PopupCentered(event, w, mode)
XEvent * event;
Widget w;
XtGrabKind mode;
{
    Boolean get_from_cursor = FALSE;
    Arg args[3];
    Cardinal num_args;
    Dimension width, height, b_width;
    int x, y, max_x, max_y;

    XtRealizeWidget(w);

    if (event == NULL)
	get_from_cursor = TRUE;
    else {
	switch (event->type) {
	case ButtonPress:
	case ButtonRelease:
	    x = event->xbutton.x_root;
	    y = event->xbutton.y_root;
	    break;
	case KeyPress:
	case KeyRelease:
	    x = event->xkey.x_root;
	    y = event->xkey.y_root;
	    break;
	default:
	    get_from_cursor = TRUE;
	    break;
	}
    }

    if (get_from_cursor) {
	Window root, child;
	int win_x, win_y;
	unsigned int mask;
	
	XQueryPointer(XtDisplay(w), XtWindow(w),
		      &root, &child, &x, &y, &win_x, &win_y, &mask);
    }

    num_args = 0;
    XtSetArg(args[num_args], XtNwidth, &width); num_args++;
    XtSetArg(args[num_args], XtNheight, &height); num_args++;
    XtSetArg(args[num_args], XtNborderWidth, &b_width); num_args++;
    XtGetValues(w, args, num_args);

    width += 2 * b_width;
    height += 2 * b_width;

    x -= ((int) width/2);
    if (x < 0) 
	x = 0;
    if ( x > (max_x = (int) (XtScreen(w)->width - width)) )
	x = max_x;

    y -= ( (Position) height/2 );
    if (y < 0) 
	y = 0;
    if ( y > (max_y = (int) (XtScreen(w)->height - height)) )
	y = max_y;
  
    num_args = 0;
    XtSetArg(args[num_args], XtNx, x); num_args++;
    XtSetArg(args[num_args], XtNy, y); num_args++;
    XtSetValues(w, args, num_args);

    XtPopup(w, mode);
}

/*	Function Name: _PopdownFileDialog
 *	Description: Destroys the file dialog, and calls the correct function.
 *	Arguments:  w - a child of the dialog widget.
 *                  client_data - TRUE if command was sucessful.
 *                  junk - ** UNUSED **.
 *	Returns: none.
 */

/* ARGSUSED */

void 
_PopdownFileDialog(w, client_data, junk)
Widget w;
XtPointer client_data, junk;
{
    Widget dialog = XtParent(w);
    caddr_t file_info_ptr;
    FileDialogInfo * file_info;

    if (XFindContext(XtDisplay(dialog), (Window) dialog, file_dialog_context,
		     &file_info_ptr) == XCNOENT) {
	SetMessage(global_screen_data.info_label,	
		   "Error while trying to find Context\nAborting...");	
    }

    (void) XDeleteContext(XtDisplay(dialog), (Window)dialog, 
			  file_dialog_context);

    file_info = (FileDialogInfo *) file_info_ptr;

    if ( ((Boolean) client_data) == TRUE ) {
	String filename = XawDialogGetValueString(dialog);

	(*file_info->func)(w, file_info->data, filename); /* call handler */
    }

    XtFree( (XtPointer) file_info); /* Free data. */

    XtPopdown(XtParent(dialog));
    XtDestroyWidget(XtParent(dialog)); /* Remove file dialog. */
}

/************************************************************
 *
 * Functions for dealing with the Resource Box.
 *
 ************************************************************/

/*    Function Name: GetNamesAndClasses
 *    Description: Gets a list of names and classes for this widget.
 *    Arguments: node - this widget's node.
 *                 names, classes - list of names and classes. ** RETURNED **
 *    Returns: none.
 */

void
GetNamesAndClasses(node, names, classes)
WNode * node;
char *** names, ***classes;
{
    int i, total_widgets;
    WNode * temp = node;

    for (total_widgets = 1 ; temp->parent != NULL ;
       total_widgets++, temp = temp->parent) { } /* empty for */

    *names = (char **) XtMalloc(sizeof(char *) * (total_widgets + 1));
    *classes = (char **) XtMalloc(sizeof(char *) * (total_widgets + 1));

    (*names)[total_widgets] = (*classes)[total_widgets] = NULL;

    for ( i = (total_widgets - 1); i >= 0 ; node = node->parent, i--) {
      (*names)[i] = node->name;
      (*classes)[i] = node->class;
    }
}

/*	Function Name: HandleGetResources
 *	Description: Gets the resources.
 *	Arguments: event - the information from the client.
 *	Returns: an error message to display.
 */

char *
HandleGetResources(event)
Event * event;
{
    GetResourcesEvent * get_event = (GetResourcesEvent *) event;
    char buf[BUFSIZ], * errors = NULL;
    int i;
    WNode * node;

    for (i = 0; i < (int)get_event->num_entries; i++) {
	node = FindNode(global_tree_info->top_node,
			get_event->info[i].widgets.ids, 
			get_event->info[i].widgets.num_widgets);

	if (node == NULL) {
	    sprintf(buf, "Editres Internal Error: Unable to FindNode.\n");
	    AddString(&errors, buf); 
	    continue;	
	}

	if (node->resources != NULL) 
	    FreeResources(node->resources);

	if (!get_event->info[i].error) {
	    node->resources = ParseResources(get_event->info + i, &errors);
	    CreateResourceBox(node, &errors);
	}
	else {
	    AddString(&errors, get_event->info[i].message);
	    AddString(&errors, "\n");
	}
    }

    return(errors);
}

/*	Function Name: CreateResourceBox
 *	Description: Creates a resource box for the widget specified.
 *	Arguments: node - the node of the widget in question.
 *                 errors - an error string.
 *	Returns: none.
 */

void
CreateResourceBox(node, errors)
WNode * node;
char ** errors;
{
    void CreateResourceBoxWidgets();
    WidgetResources * resources = node->resources;
    char ** names, ** cons_names;
    int i;

    if (global_resource_box_up) {
	AddString(errors, "Only one Resource Box can be active at a time.");
	return;
    }
    else
	global_resource_box_up = TRUE;

    if (resources->num_normal > 0) {
	names = (char **) XtMalloc(sizeof(char *) *
				   (resources->num_normal + 1));
	for (i = 0 ; i < resources->num_normal ; i++) 
	    names[i] = resources->normal[i].name;
	names[i] = NULL;
    }
    else
	names = NULL;

    if (resources->num_constraint > 0) {
	cons_names = (char **) XtMalloc(sizeof(char *) *
					(resources->num_constraint + 1));
	
	for (i = 0 ; i < resources->num_constraint ; i++) 
	    cons_names[i] = resources->constraint[i].name;
	cons_names[i] = NULL;
    }
    else
	cons_names = NULL;

    CreateResourceBoxWidgets(node, names, cons_names);
}

/*	Function Name: ParseResources
 *	Description: Parses the resource values returned from the client
 *                   into a resources structure.
 *	Arguments: info - info about a widget's resources.
 *                 error - where to place error info.
 *	Returns: The resource information.
 */

static WidgetResources * 
ParseResources(info, error)
GetResourcesInfo * info;
char **error;
{
    WidgetResources * resources;
    WidgetResourceInfo * normal;
    int i;

    resources = (WidgetResources *) XtMalloc(sizeof(WidgetResources)); 
    
    /*
     * Allocate enough space for both the normal and constraint resources,
     * then add the normal resources from the top, and the constraint resources
     * from the bottom.  This assures that enough memory is allocated, and
     * that there is no overlap.
     */

    resources->normal = (WidgetResourceInfo *) 
	            XtMalloc(sizeof(WidgetResourceInfo) * info->num_resources);

    normal = resources->normal;
    resources->constraint = resources->normal + info->num_resources - 1;

    resources->num_constraint = resources->num_normal = 0;

    for (i = 0; i < (int)info->num_resources; i++) {
	switch((int) info->res_info[i].res_type) {
	case NormalResource:
	    resources->num_normal++;
	    AddResource(info->res_info + i, normal++);	    
	    break;
	case ConstraintResource:
	    resources->num_constraint++;
	    AddResource(info->res_info + i, resources->constraint--);
	    break;
	default:
	    {
		char buf[BUFSIZ];
		sprintf(buf, "Unknown resource type %d\n", 
			info->res_info[i].res_type);
		AddString(error, buf);
	    }
	    break;
	}
    }

    /*
     * Sort the resources alphabetically. 
     */

    qsort(resources->normal, resources->num_normal,
	  sizeof(WidgetResourceInfo), CompareResourceEntries);

    if (resources->num_constraint > 0) {
	resources->constraint++;
	qsort(resources->constraint, resources->num_constraint,
	      sizeof(WidgetResourceInfo), CompareResourceEntries);
    }
    else
	resources->constraint = NULL;

    return(resources);
}

/*	Function Name: CompareResourceEntries
 *	Description: Compares two resource entries.
 *	Arguments: e1, e2 - the entries to compare.
 *	Returns: an integer >, < or = 0.
 */

static int 
CompareResourceEntries(e1, e2) 
WidgetResourceInfo *e1, *e2;
{
    return (strcmp(e1->name, e2->name));
}

/*	Function Name: AddResource
 *	Description: Parses the resource string a stuffs in individual
 *                   parts into the resource info struct.
 *	Arguments: res_info - the resource info from the event.
 *                 resource - location to stuff the resource into.
 *	Returns: none.
 */

static void
AddResource(res_info, resource) 
ResourceInfo * res_info;
WidgetResourceInfo * resource;
{
    resource->name = res_info->name;
    res_info->name = NULL;	/* Keeps it from being deallocated. */
    resource->class = res_info->class;
    res_info->class = NULL;	/* Keeps it from being deallocated. */
    resource->type = res_info->type;
    res_info->type = NULL;	/* Keeps it from being deallocated. */
}


/*	Function Name: FreeResources
 *	Description: frees the resource inforation.
 *	Arguments: resources.
 *	Returns: none.
 */

static void
FreeResources(resources) 
WidgetResources * resources;
{
    int i;

    if (resources->num_normal > 0) {
	for (i = 0; i < resources->num_normal; i++) {
	    XtFree(resources->normal[i].name);
	    XtFree(resources->normal[i].class);
	    XtFree(resources->normal[i].type);
	}
	XFree((char *)resources->normal);
    }

    if (resources->num_constraint > 0) {
	for (i = 0; i < resources->num_constraint; i++) {
	    XtFree(resources->constraint[i].name);
	    XtFree(resources->constraint[i].class);
	    XtFree(resources->constraint[i].type);
	}
	XFree((char *)resources->constraint);
    }

    XFree((char *)resources);
}
	

/*	Function Name: CheckDatabase
 *	Description: Checks to see if the node is in the database.
 *	Arguments: db - the db to check
 *                 names, clases - names and clases, represented as quarks.
 *	Returns: True if this entry is found.
 */

Boolean
CheckDatabase(db, names, classes)
XrmDatabase db;
XrmQuarkList names, classes;
{
    XrmRepresentation junk;
    XrmValue garbage;

    return(XrmQGetResource(db, names, classes, &junk, &garbage));
}

/*	Function Name: Quarkify
 *	Description: Quarkifies the string list specifed.
 *	Arguments: list - list of strings to quarkify
 *                 ptr - an additional string to quarkify.
 *	Returns: none.
 */

XrmQuarkList
Quarkify(list, ptr)
char ** list;
char * ptr;
{
    int i;
    char ** tlist;
    XrmQuarkList quarks, tquarks;

    for (i = 0, tlist = list; *tlist != NULL; tlist++, i++) {}
    if (ptr != NULL)
	i++;
    i++;			/* leave space for NULLQUARK */

    quarks = (XrmQuarkList) XtMalloc(sizeof(XrmQuark) * i);

    for (tlist = list, tquarks = quarks; *tlist != NULL; tlist++, tquarks++) 
	*tquarks = XrmStringToQuark(*tlist);

    if (ptr != NULL) 
	*tquarks++ = XrmStringToQuark(ptr);
	
    *tquarks = NULLQUARK;
    return(quarks);
}

/*	Function Name: ExecuteOverAllNodes
 *	Description: Executes the given function over all nodes.
 *	Arguments: top_node - top node of the tree.
 *                 func - the function to execute.
 *                 data - a data pointer to pass to the function.
 *	Returns: none
 */

void
ExecuteOverAllNodes(top_node, func, data)
WNode * top_node;
void (*func)();
XtPointer data;
{
    int i;

    (*func)(top_node, data);

    for (i = 0; i < top_node->num_children; i++) 
	ExecuteOverAllNodes(top_node->children[i], func, data);
}

/*	Function Name: InsertWidgetFromNode
 *	Description: Inserts the widget info for this widget represented
 *                   by this node.
 *	Arguments: stream - the stream to insert it info into.
 *                 none - the widget node to insert.
 *	Returns: none
 */

void
InsertWidgetFromNode(stream, node)
ProtocolStream * stream;
WNode * node;
{
    WNode *temp;
    unsigned long * widget_list;
    register int i, num_widgets;

    for (temp = node, i = 0; temp != 0; temp = temp->parent, i++) {}

    num_widgets = i;
    widget_list = (unsigned long *) 
	          XtMalloc(sizeof(unsigned long) * num_widgets);

    /*
     * Put the widgets into the list.
     * Make sure that they are inserted in the list from parent -> child.
     */

    for (i--, temp = node; temp != 0; temp = temp->parent, i--) 
	widget_list[i] = temp->id;
	
    _XEditResPut16(stream, num_widgets);	/* insert number of widgets. */
    for (i = 0; i < num_widgets; i++) 	/* insert Widgets themselves. */
	_XEditResPut32(stream, widget_list[i]);
    
    XtFree((char *)widget_list);
}

/*	Function Name: GetFailureMesssage
 *	Description: returns the message returned from a failed request.
 *	Arguments: stream - the protocol stream containing the message.
 *	Returns: message to show.
 */

char * 
GetFailureMessage(stream)
ProtocolStream * stream;
{
    char * return_str;

    if (_XEditResGetString8(stream, &return_str)) 
	return(return_str);

    return(XtNewString("Unable to unpack protocol request."));
}

/*	Function Name: ProtocolFailure
 *	Description: Gets the version of the protocol the client is
 *                   willing to speak.
 *	Arguments: stream - the protocol stream containing the message.
 *	Returns: message to show.
 */

char * 
ProtocolFailure(stream)
ProtocolStream * stream;
{
    char buf[BUFSIZ];
    unsigned char version;

    if (!_XEditResGet8(stream, &version)) 
	return(XtNewString("Unable to unpack protocol request."));

    sprintf(buf, "This version of editres uses protocol version %d.\n%s%d",
	    CURRENT_PROTOCOL_VERSION,
	    "But the client speaks version ", (int) version);
    return(XtNewString(buf));
}
	
