/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (c) 1995-1999.  The Regents of the University of California.  All     */
/*   rights reserved.                                                      */
/*                                                                         */
/*   This work was produced at the University of California, Lawrence      */
/*   Livermore National Laboratory (UC LLNL) under contract no.            */
/*   W-7405-ENG-48 (Contract 48) between the U.S. Department of Energy     */
/*   (DOE) and The Regents of the University of California (University)    */
/*   for the operation of UC LLNL.  Copyright is reserved to the           */
/*   University for purposes of controlled dissemination,                  */
/*   commercialization through formal licensing, or other disposition      */
/*   under terms of Contract 48; DOE policies, regulations and orders;     */
/*   and U.S. statutes.  The rights of the Federal Government are          */
/*   reserved under Contract 48 subject to the restrictions agreed upon    */
/*   by the DOE and University.                                            */
/*                                                                         */
/*                                                                         */
/*                              DISCLAIMER                                 */
/*                                                                         */
/*   This software was prepared as an account of work sponsored by an      */
/*   agency of the United States Government.  Neither the United States    */
/*   Government nor the University of California nor any of their          */
/*   employees, makes any warranty, express or implied, or assumes any     */
/*   liability or responsibility for the accuracy, completeness, or        */
/*   usefulness of any information, apparatus, product, or process         */
/*   disclosed, or represents that its specific commercial products,       */
/*   process, or service by trade name, trademark, manufacturer, or        */
/*   otherwise, does not necessarily constitute or imply its               */
/*   endorsement, recommendation, or favoring by the United States         */
/*   Government or the University of California. The views and opinions    */
/*   of the authors expressed herein do not necessarily state or reflect   */
/*   those of the United States Government or the University of            */
/*   California, and shall not be used for advertising or product          */
/*   endorsement purposes.                                                 */
/*                                                                         */
/*   Permission to use, copy, modify and distribute this software and its  */
/*   documentation for any non-commercial purpose, without fee, is         */
/*   hereby granted, provided that the above copyright notice and this     */
/*   permission notice appear in all copies of the software and            */
/*   supporting documentation, and that all UC LLNL identification in      */
/*   the user interface remain unchanged.  The title to copyright LLNL     */
/*   XDIR shall at all times remain with The Regents of the University     */
/*   of California and users agree to preserve same. Users seeking the     */
/*   right to make derivative works with LLNL XDIR for commercial          */
/*   purposes may obtain a license from the Lawrence Livermore National    */
/*   Laboratory's Technology Transfer Office, P.O. Box 808, L-795,         */
/*   Livermore, CA 94550.                                                  */
/*                                                                         */
/***************************************************************************/
/***************************************************************************/

#ifdef NEXT
#include <bsd/sys/unistd.h>
#else
#include <unistd.h>
#endif
#include <sys/time.h>
#include <ctype.h>
#include <Xm/TextF.h>
#include <Xm/Protocols.h>
#include <errno.h>
#include "xdir.h"
#include "list.h"
#include "str.h"
#include "history.h"

static int volume = 0;

static char *msg1 = "Operation aborted.\n\nThis directory window might be out of date.\n\nClick on sun icon in toolbar to refresh.";

static char *msg2 = "Operation aborted.\n\nSource and sink directory windows might be out of date.\n\nClick on sun icon in toolbar to refresh a directory window.";

char *entry_to_rel_path();

extern Display *display;
extern Window root_window;
extern int screen;
extern int need_to_save_prefs;
extern int diagnostics;
extern struct dirwin_st *dirwin_head;
extern struct st_host_info hinfo[];
extern int use_last_dir;


/*
 * add_wm_delete_window_cb - Add callback for the WM_DELETE_WINDOW protocol
 *                           for the shell that is the ancestor of widget.
 *                           Set "unmap" to True if the window manager should
 *                           unmap the window.
 */
void
add_wm_delete_window_cb(widget, callback, client_data, unmap)
Widget widget;
XtCallbackProc callback;
XtPointer client_data;
int unmap;
{
	Atom WM_DELETE_WINDOW;
	Widget shell;

	/* First find the dialog's shell widget */
	for (shell = widget; !XtIsShell(shell); shell = XtParent(shell));

	/* Don't want toolkit to handle Close action */
	if (unmap)
		XtVaSetValues(shell, XmNdeleteResponse, XmUNMAP, NULL);
	else
		XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);

	/* Install the WM_DELETE_WINDOW atom */
	WM_DELETE_WINDOW = XmInternAtom(display, "WM_DELETE_WINDOW", False);

	/* Add callback for the WM_DELETE_WINDOW protocol */
	XmAddWMProtocolCallback(shell, WM_DELETE_WINDOW, callback, client_data);
}


/*
 * beep - Emit a beep.
 */
void
beep()
{
    XBell(display, volume);
}


/*
 * vertical_sb - Returns the vertical scrollbar of the list widget "w_list".
 */
Widget
vertical_sb(widget)
Widget widget;
{
	Widget  w_vertical_sb;

	XtVaGetValues(widget, XmNverticalScrollBar, &w_vertical_sb, NULL);
	return w_vertical_sb;
}


/*
 * quit - Quit application.  w_parent is the widget to center the error
 *        and question dialogs over.
 */
void
quit(w_parent)
Widget w_parent;
{
	/* See if preferences need to be saved */
	if (need_to_save_prefs && question_dialog(
			"Do you want to save the user preferences?", w_parent)
			&& save_prefs()) {
		warn("Unable to save user preferences.", w_parent);
	}

	/* Get rid of any remaining temporary view directories */
	delete_all_viewer_dirs();

	if (save_history(w_parent) < 0)
		warn("Unable to save history.", w_parent);

	exit(0);
}


/*
 * cb_quit - Callback to quit application.
 */
void
cb_quit(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
	struct dirwin_st *dirwin = (struct dirwin_st *)client_data;

	/* Start operation */
	if (!start_op(False))
		return;

	if (question_dialog("Do you really want to terminate Xdir?",
			dirwin->w_shell))
		quit(dirwin->w_shell);

	/* End operation */
	end_op();
}


/*
 * determine_true_path - Returns the actual path of the directory "path"
 *                       in "true_path" (which might differ from "path"
 *                       because of aliases).  Set print_error_msg to
 *                       True if error messages are to be written to
 *                       the log.  Set "cfetch_ok" to True if it is ok
 *                       to fetch the true path from the cache.  Set
 *                       "cstore_ok" to True if it is ok to use the
 *                       true path to update the cache.  The caller is
 *                       responsible for freeing "true_path" with XtFree().
 *                       Returns 0 if successful, -3 for broken connection,
 *                       -6 for stop button pressed, and -1 for other
 *                       errors.  "path" must be a full path.
 */
determine_true_path(host, path, print_error_msg, cstore_ok, cfetch_ok,
                    true_path)
int host;
char *path;
int print_error_msg;
int cstore_ok;
int cfetch_ok;
char **true_path;
{
    int retval;

	/* Can the directory mapping cache be used? */
	if (cfetch_ok && (host != LOCAL)) {
		if (fetch_truepath_from_cache(host, path, true_path))
			return 0;
	}

	/* Get true path */
    if (host == LOCAL) {
        if (local_cd(path, print_error_msg) < 0)
            return -1;
        if (local_pwd(true_path) != 0)
            fatal_error("Unable to get local working directory");
    } else {
        if ((retval = remote_cd(host, path, print_error_msg)) < 0)
            return retval;
        if ((retval = remote_pwd(host, true_path)) < 0)
            return retval;
		if (cstore_ok)
			add_truepath_to_cache(host, path, *true_path);
    }

    return 0;
}


/*
 * report_client_timeout - Write notice to diagnostics log that the
 *                         "host" has timed out.
 */
void
report_client_timeout(host)
int host;
{
	char *msg;

    if (diagnostics >= VERBOSE) {
		msg = XtMalloc(strlen(hinfo[host].hostname)+100);
		sprintf(msg, "Timed out waiting for remote host %s to respond.\n",
			hinfo[host].hostname);
		write_log(msg);
		XtFree(msg);
	}
}


/*
 * get_host - Return array index to free host structure.  Character string
 *            Fields are initialized to zeros and NULLs.  Returns -1 if 
 *            maximum number of hosts exceeded.
 */
get_host()
{
	int host;

	/* Use first free structure */
	for (host=1; host<MAXHOSTS; host++)
		if (!hinfo[host].in_use) {
			hinfo[host].homedir = NULL;
			hinfo[host].hostname = NULL;
			hinfo[host].username = NULL;
			hinfo[host].password = NULL;
			hinfo[host].account = NULL;
			hinfo[host].proxy_host = NULL;
			hinfo[host].last_reply_time = 0;
			hinfo[host].in_use = True;
			return host;
		}

	/* No free structure available */
	return -1;
}


/*
 * release_host - Free the host structure referenced by "host".  Also free
 *                memory pointed to by some of the fields.
 */
void
release_host(host)
int host;
{
	XtFree(hinfo[host].homedir);
	XtFree(hinfo[host].hostname);
	XtFree(hinfo[host].username);
	XtFree(hinfo[host].password);
	XtFree(hinfo[host].account);
	XtFree(hinfo[host].proxy_host);
	hinfo[host].in_use = False;
}


/*
 * center_dialog - Center dialog "w_dialog" in center of display.
 */
void
center_dialog(w_dialog)
Widget w_dialog;
{
	Screen *screenptr = ScreenOfDisplay(display, screen);
	Dimension width;
	Dimension height;
	int screen_width = WidthOfScreen(screenptr);
	int screen_height =  HeightOfScreen(screenptr);

	/* Center dialog over display */
	XtVaGetValues(w_dialog, XmNwidth, &width, XmNheight, &height, NULL);
	XtVaSetValues(w_dialog, XmNx, (Position)((screen_width-(int)width)/2), XmNy,
		(Position)((screen_height-(int)height)/2), NULL);
}


/*
 * postorder_selected_items - Traverse in postorder the directory entry
 *                            specified by "einfo" and add each entry's
 *                            relative path name to the tail of the linked
 *                            specified by "head" and "tail".
 */
void
postorder_selected_items(einfo, head, tail)
struct entry_info *einfo;
struct entry_link **head;
struct entry_link **tail;
{
	int i;
	struct dirwin_st *dirwin = einfo->dirwin;
	struct entry_link *ptr;

	if (einfo->expanded) {
		for (i=einfo->indx+1; i<dirwin->nentries; i++)
			if (dirwin->entries[i].level <= einfo->level)
				break;
			else if (dirwin->entries[i].level == einfo->level+1) {
				postorder_selected_items(&dirwin->entries[i], head, tail);
		}
	}
	if (einfo->state == SELECTED) {
		ptr = XtNew(struct entry_link);
		ptr->entry = entry_to_rel_path(einfo);
		ptr->next = NULL;
		if (*head == NULL) {
			*head = ptr;
			*tail = ptr;
		} else {
			(*tail)->next = ptr;
			*tail = ptr;
		}
	}
}


/*
 * cb_disconnect - Callback to disconnect host.
 */
void
cb_disconnect(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
	struct dirwin_st *dirwin = (struct dirwin_st *)client_data;
	struct dirwin_st *dwin;
	struct dirwin_st *next;
	int host;
	static char *msg = "Disconnecting from this host will terminate Xdir.  Continue disconnecting?";

	/* Start operation */
	if (!start_op(False))
		return;

    /* Clear error flag */
    raise_okflag();

	/* Will disconnecting host cause application to terminate? */
	dwin = dirwin_head;
	while (dwin && (dwin->host == dirwin->host))
		dwin = dwin->next;
	if (dwin == NULL) {
		if (question_dialog(msg, dirwin->w_shell))
			quit(dirwin->w_shell);
		else {
			end_op();
			return;
		}
	}

	/* Close all directory windows associated with host */
	host = dirwin->host;
	dwin = dirwin_head;
	while (dwin) {
		next = dwin->next;
		if (dwin->host == host)
			close_directory_window(dwin);
		dwin = next;
	}

	/* End operation */
	end_op();
}


/*
 * cb_map_dialog - Map the dialog (indicated by "widget") over the window
 *                 specified by "client_data".
 */
void
cb_map_dialog(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
	Window window = (Window)client_data;
	Window child;
	XWindowAttributes xwa;
	Dimension w;
	Dimension h;
	int x;
	int y;

	/* Get dialog's dimensions */
	XtVaGetValues(widget, XmNwidth, &w, XmNheight, &h, NULL);

	/* Get bottom window's geometry */
	if (!XGetWindowAttributes(display, window, &xwa))
		fatal_error("Bug in cb_map_dialog()");
	XTranslateCoordinates(display, window, root_window, 0, 0, &x, &y, &child);

	/* Center dialog above bottom window */
	XtVaSetValues(widget, XmNx, x+(int)(xwa.width-w)/2, XmNy,
		y+(int)(xwa.height-h)/2, NULL);
}


/*
 * create_unique_name - Create a unique file name by, if necessary,
 *                      prefixing name with "." followed by a integer
 *                      that ranges from 1 to 99.  If successful, a
 *                      pointer to the new name is returned.  The
 *                      memory is in a static buffer local to this
 *                      routine.  If unsuccessful, NULL is returned.
 *                      "file_path" should be the full path name of
 *                      the file.
 */
char *
create_unique_name(file_path)
char *file_path;
{
	static char new_path[MAXPATHLEN+1];
	int i;

	/* First see if supplied name is unique */
	if (access(file_path, 0) < 0) {
		if (errno == ENOENT) {
			strcpy(new_path, file_path);
			return new_path;
		} else
			return NULL;
	}

	/* Try concatenating a "." followed by an integer */
	for (i=1; i<100; i++) {
		sprintf(new_path, "%s.%d", file_path, i);
		if (access(new_path, 0) < 0) {
			if (errno == ENOENT)
				return new_path;
			else
				return NULL;
		}
	}

	/* Ran out of suffices */
	return NULL;
}


/*
 * can_create_sinkfile - Returns True if a writable file specifed by
 *                       "snk_path" can be created; else returns False.
 */
can_create_sinkfile(snk_path)
char *snk_path;
{
	char *snk_dir;

    parse_path(SYS_UNIX, snk_path, &snk_dir, NULL);
    if (access(snk_path, W_OK) < 0)
        if (errno == ENOENT) {   /* File does not exist */
            if (access(snk_dir, W_OK) < 0) {
                report_perror(snk_path);
                XtFree(snk_dir);
                return False;
            }
        } else if (errno == EACCES) {    /* Permission denied */
            if (access(snk_dir, W_OK) < 0) {
                report_perror(snk_path);
                XtFree(snk_dir);
                return False;
            }
            if (chmod(snk_path, 0600) < 0) {
                report_perror(snk_path);
                XtFree(snk_dir);
                return False;
            }
        } else {
            report_perror(snk_path);
            XtFree(snk_dir);
            return False;
        }
    XtFree(snk_dir);

	return True;
}


/*
 * cb_iconify_all - Callback to iconify all top-level windows.
 */
void
cb_iconify_all(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
	/* Start operation */
	if (!start_op(False))
		return;

	/* Iconify all the windows */
	iconify_diag_window();
	iconify_lprefs_window();
	iconify_vprefs_window();
	iconify_fprefs_window();
	iconify_gprefs_window();
	iconify_directory_windows();
	iconify_file_viewer_windows();

	/* End operation */
	end_op();
}


/*
 * cb_deiconify_all - Callback to de-iconify all top-level windows.
 */
void
cb_deiconify_all(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
	/* Start operation */
	if (!start_op(False))
		return;

	/* Deiconify all the windows */
	deiconify_diag_window();
	deiconify_lprefs_window();
	deiconify_vprefs_window();
	deiconify_fprefs_window();
	deiconify_gprefs_window();
	deiconify_directory_windows();
	deiconify_file_viewer_windows();

	/* End the operation */
	end_op();
}


/*
 * cvt_to_lower - Convert string to lower case.
 */
void
cvt_to_lower(str)
char *str;
{
	int i;

	for (i=strlen(str); i>=0; i--)
		str[i] = tolower(str[i]);
}


/*
 * cvt_to_upper - Convert string to upper case.
 */
void
cvt_to_upper(str)
char *str;
{
	int i;

	for (i=strlen(str); i>=0; i--)
		str[i] = toupper(str[i]);
}


/*
 * add_commas - Converts "value" to a string, with each group of digits
 *              delimited with a comma (e.g., long integer 1234567 is
 *              converted to the string "1,234,567".   Call XtFree() to
 *              free returned string.
 */

char *
add_commas(value)
long value;
{
	char buf[25];
	char *number;
	int negative = False;
	int ndigits;
	int ncommas;
	int nlead;
	int i;
	int j;

	/* Easier to work with positive integer */
	if (value < 0) {
		negative = True;
		value = -value;
	}

	/* Convert integer to string and add commas */
	sprintf(buf, "%ld", value);
	ndigits = strlen(buf);
	ncommas = (ndigits-1)/3;
	if (negative) {
		number = XtMalloc(ndigits+ncommas+2);
		number[0] = '-';
		j = 1;
	} else {
		number = XtMalloc(ndigits+ncommas+1);
		j = 0;
	}
	nlead = ndigits-((ndigits-1)/3)*3;
	for (i=0; i<nlead; i++)
		number[j++] = buf[i];
	while (i < ndigits) {
		number[j++] = ',';
		number[j++] = buf[i++];
		number[j++] = buf[i++];
		number[j++] = buf[i++];
	}
	number[j] = '\0';
	return number;
}


/*
 * cstring_to_text - Convert compound string "string" to a null-delimited
 *                   string, which is returned.  Call XtFree() to free
 *                   returned string.
 */
char *
cstring_to_text(string)
XmString string;
{
	char *text;

#ifdef XmFONTLIST_DEFAULT_TAG    /* Motif 1.2.x */
	if (!XmStringGetLtoR(string, XmFONTLIST_DEFAULT_TAG, &text))
#else							 /* Motif 1.1.x */
	if (!XmStringGetLtoR(string, XmSTRING_DEFAULT_CHARSET, &text))
#endif
		fatal_error("Trouble in cstring_to_text()");

		return text;
}

/*
 * text_to_cstring - Convert null-delimited string "text" to a compound
 *                   string, which is returned.  Call XmStringFree() to
 *                   free returned string.
 */
XmString
text_to_cstring(text)
char *text;
{
#ifdef XmFONTLIST_DEFAULT_TAG    /* Motif 1.2.x */
	return XmStringCreateLtoR(text, XmFONTLIST_DEFAULT_TAG);
#else                            /* Motif 1.1.x */
	return XmStringCreateLtoR(text, XmSTRING_DEFAULT_CHARSET);
#endif
}


/*
 * traverse_to_widget - Traverse to specified widget.
 */
void
traverse_to_widget(widget)
Widget widget;
{
	/* Yep, got to do this twice */
	XmProcessTraversal(widget, XmTRAVERSE_CURRENT);
	XmProcessTraversal(widget, XmTRAVERSE_CURRENT);
}


/*
 * starting_dir - Returns the path of the first directory to be entered
 *                after connecting to "host".  Returns 0 if successful
 *                -3 for broken connection, -6 for stop button pushed,
 *                and -1 for other errors.  If successful, the caller
 *                is responsible for calling XtFree() to free returned
 *                string, "path".
 */
starting_dir(host, path)
int host;
char **path;
{
	struct sl_struct *list;
	int retval;

	/* Use last referenced directory if requested */
	if (use_last_dir) {
		retrieve_history(DIRECTORY, hinfo[host].hostname, &list, False);
		if (list->nentries > 0) {
			retval = determine_true_path(host, list->entries[0], False, False,
				False, path);
			release_array_list(list);
			if (retval != -1)
				return retval;
		} else
			release_array_list(list);
	}

	/* Default to home directory */
	*path = XtNewString(hinfo[host].homedir);
	return 0;
}


/*
 * cmp_long_vms_entries - Compares two long VMS entries (basically the
 *                        first blank-delimited tokens in "string1" and
 *                        "string2".  Returns negative value if "string1"
 *                        is less than "string2, 0 if "string1" equals
 *                        string2, and positive value if "string1" is
 *                        greater than "string2".
 */
cmp_long_vms_entries(string1, string2)
char *string1;
char *string2;
{
	char str1[MAXPATHLEN+1];
	char str2[MAXPATHLEN+1];

	/* Make copies of strings because strtok() modifies input string */
	strcpy(str1, string1);
	strcpy(str2, string2);

	/* Strip off entry names */
	if (!strtok(str1, " \t") || !strtok(str2, " \t"))
		fatal_error("Bug in cmp_long_vms_entries()");

	/* Make comparison */
	return strcmp(str1, str2);
}


/*
 * cmp_symbol_entries - Compares two strings, starting with the second
 *                      character of each string.  Returns negative
 *                      value if "string1" is less than "string2,
 *                      0 if "string1" equals "string2", and positive
 *                      value if "string1" is greater than "string2".
 */
cmp_symbol_entries(string1, string2)
char *string1;
char *string2;
{
	return strcmp(string1+1, string2+1);
}


/*
 * sort_symbol_entries - Sort the "n" strings pointed to by the array of
 *                       character pointers "s".  If a string ends with
 *                       '@', '*', '=', or '/', ignore the last character
 *                       when comparing two strings.
 */
void
sort_symbol_entries(s, n)
char *s[];
int n;
{
	int i;
	int len;
	char *p;
	char ch;

	/* Move special character to first position in string, else use space */
	for (i=0; i<n; i++) {
		len = strlen(s[i]);
		switch (s[i][len-1]) {
		case '@':
		case '*':
		case '=':
		case '/':
			p = XtMalloc(len+1);
			p[0] = s[i][len-1];
			s[i][len-1] = '\0';
			break;
		default:
			p = XtMalloc(len+2);
			p[0] = ' ';
		}
		strcpy(p+1, s[i]);
		XtFree(s[i]);
		s[i] = p;
	}

	/* Sort the entries */
	quicksort(s, n, cmp_symbol_entries);

	/* Move special character back to end of each string */
	for (i=0; i<n; i++) {
		ch = s[i][0];
		p = s[i];
		while (*(p+1)) {
			*p = *(p+1);
			p++;
		}
		if (ch != ' ')
			*p++ = ch;
		*p = '\0';
	}
}


/*
 * add_timevals - Add times "time1" and "time2" to produce "total_time".
 *                All times are represented using the standard timeval
 *                structure.
 */
void
add_timevals(time1, time2, total_time)
struct timeval *time1;
struct timeval *time2;
struct timeval *total_time;
{
	long carry_secs;

	total_time->tv_sec = time1->tv_sec+time2->tv_sec;
	total_time->tv_usec = time1->tv_usec+time2->tv_usec;
	carry_secs = total_time->tv_usec/1000000;
	total_time->tv_usec = total_time->tv_usec-1000000*carry_secs;
	total_time->tv_sec += carry_secs;
}


/*
 * cmp_timevals - Returns negative value if "time1" is less than "time2",
 *                0 if "time1" equals "time2", and positive value if
 *                "time1" is greater than "time2".  All times are
 *                 represented using the standard timeval structure.
 */
cmp_timevals(time1, time2)
struct timeval *time1;
struct timeval *time2;
{
	if (time1->tv_sec > time2->tv_sec)
		return 1;
	else if (time1->tv_sec < time2->tv_sec)
		return -1;
	else if (time1->tv_usec > time2->tv_usec)
		return 1;
	else if (time1->tv_usec < time2->tv_usec)
		return -1;
	else
		return 0;
}


/*
 * dirwin_out_of_date_alert - Alert the user that the directory window might
 *                            out of date because of the aborted operation.
 *                            The alert dialog is placed over the directory
 *                            window specified by "dirwin".
 */
void
dirwin_out_of_date_alert(dirwin)
struct dirwin_st *dirwin;
{
	info_dialog(msg1, dirwin->w_shell);
}


/*
 * dirwins_out_of_date_alert - Alert the user that the directory windows might
 *                             out of date because of the aborted operation.
 *                             The alert dialog is placed over the directory
 *                             window specified by "dirwin".
 */
void 
dirwins_out_of_date_alert(dirwin)
struct dirwin_st *dirwin;
{
	info_dialog(msg2, dirwin->w_shell);
}


/*
 * set_textfield - Set the textfield widget "widget" to the character
 *                 string "string".  If the textfield already has this
 *                 value, this routine is a no-op (to prevent the widget
 *                 from blinking).
 */
void
set_textfield(widget, string)
Widget widget;
char *string;
{
	char *old_string;

	old_string = XmTextFieldGetString(widget);
	if (strcmp(string, old_string))
		XmTextFieldSetString(widget, string);
	XtFree(old_string);
}



syntax highlighted by Code2HTML, v. 0.9.1