/***************************************************************************/
/***************************************************************************/
/*                                                                         */
/*   (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.                                                  */
/*                                                                         */
/***************************************************************************/
/***************************************************************************/

#include <Xm/Xm.h>
#include <string.h>
#include "xdir.h"
#include "resources.h"
#include "list.h"
#include "history.h"

struct dirwin_st *dirwin_head = NULL;

struct dirwin_st *create_dir_window();
char *entry_to_rel_path();
char *links_to_path();
char **path_to_links();
struct entry_info *rel_path_to_entry();
void set_first_visible_entry();

extern struct st_host_info hinfo[];
extern Display *display;
extern int initial_layout;
extern AppData app_data;
extern int initial_tunneling_mode;
extern int initial_dotfiles_mode;
extern int initial_cache_mode;


/*
 * display_dir - Display the directory specified by "path" for the
 *               host specified by "host".  If the directory is already
 *               being displayed, pop its window to the top.  If the
 *               directory is not already displayed and if "dirwin" is
 *               is non-null then display the directory in the window 
 *               specified by "dirwin".  If the directory is not already
 *               displayed and if "dirwin" is null, then create a new
 *               directory window to display the directory in.  Set
 *               "refresh_existing" to True if you want a directory
 *               window that already displays "dir" to be refreshed.
 *               Set "raise" to True is you want a directory window
 *               that already displays "dir" to be raised.  Note that
 *               "refresh_existing" and "raise" are independent.  Set
 *               "cfetch_ok" to True if it is ok to fetch the directory
 *               list from the cache.  Set "cstore_ok" to True if it is
 *               ok to use the directory list to update the cache.  Returns 
 *               0 if successful, -3 for lost connection, -6 for stop
 *               button pushed, and -1 for other errors.
 */
display_dir(host, dirwin, path, refresh_existing, raise, cstore_ok, cfetch_ok)
int host;
struct dirwin_st *dirwin;
char *path;
int refresh_existing;
int raise;
int cstore_ok;
int cfetch_ok;
{
	struct dirwin_st *temp_dirwin;
	char *true_path;
	int already_displayed = False;
	struct sl_struct *dlist;
	int retval;
	int i;
	int width;
	int layout;
	int tunneling_mode;
	int dotfiles_mode;
	int cache_mode;
	struct entry_info *einfo;
	int need_to_create;
	char *temp;
	char *username;
	char *anchor_rel_path = NULL;
	int indx;
	int used_cache = False;

	/* This could take some time */
	use_busy_cursor();

	/* Determine true path of directory */
	retval = determine_true_path(host, path, True, cstore_ok, cfetch_ok,
		&true_path);
	if (retval < 0) {
		restore_prev_cursor();
		return retval;
	}

	/* Is directory already being displayed? */
	if (is_dir_displayed(host, true_path, &temp_dirwin, &einfo)) {
		if (einfo)
			collapse_subtree(&einfo, True);
		else {
			dirwin = temp_dirwin;
			already_displayed = True;
			if (raise)
				XMapRaised(display, XtWindow(dirwin->w_shell));
			if (refresh_existing) {
				if (dirwin->layout == TREE) {
					retval = re_expand_tree(dirwin, cstore_ok, cfetch_ok);
					XtFree(true_path);
					restore_prev_cursor();
					switch (retval) {
					case 1:
						report_cache_use(dirwin, True);
						return 0;
					case 0:
						report_cache_use(dirwin, False);
					default:
						return retval;
					}
				}
			} else {
				XtFree(true_path);
				restore_prev_cursor();
				return 0;
			}
		}
	}

	/* Will a new dialog be needed to display the directory? */
	if (already_displayed || (dirwin && (dirwin->host == host)
			&& dirwin->tunneling_mode)) {
		need_to_create = False;
		layout = dirwin->layout;
		dotfiles_mode = dirwin->dotfiles_mode;
		if (raise)
			XMapRaised(display, XtWindow(dirwin->w_shell));
	} else {
		need_to_create = True;
		if (dirwin) {
			tunneling_mode = dirwin->tunneling_mode;
			layout = dirwin->layout;
			dotfiles_mode = dirwin->dotfiles_mode;
			cache_mode = dirwin->cache_mode;
		} else {
			tunneling_mode = initial_tunneling_mode;
			layout = initial_layout;
			dotfiles_mode = initial_dotfiles_mode;
			cache_mode = initial_cache_mode;
		}
	}

	/* Get directory list */
	retval = get_dirlist(host, true_path, layout, dotfiles_mode, cstore_ok,
		cfetch_ok, &dlist);
	if (retval < 0) {
		XtFree(true_path);
		restore_prev_cursor();
		return retval;
	}
	if (retval == 1)
		used_cache = True;

	/* Remember topmost visible entry */
	if (already_displayed && refresh_existing && dirwin->nentries) {
		indx = dirwin->first_visible_row;
		if (indx)
			anchor_rel_path = entry_to_rel_path(&dirwin->entries[indx]);
	}

	/* Create dir window and inherit modes from "parent" dir window */
	if (need_to_create) {
		dirwin = create_dir_window();
		dirwin->host = host;
		dirwin->layout = layout;
		dirwin->tunneling_mode = tunneling_mode;
		dirwin->dotfiles_mode = dotfiles_mode;
		dirwin->cache_mode = cache_mode;
		update_goto_menu(dirwin);
		update_launch_menus(dirwin);
		update_xfer_mode_menu(dirwin);
		update_viewed_files_menu(dirwin);
		if (host == LOCAL)
			XtSetSensitive(dirwin->w_quotedItem, False);
	}

	/* Update directory name */
	XtFree(dirwin->dirname);
    dirwin->dirname = XtNewString(true_path);

	/* Update title bar */
	if (host == LOCAL)
		username = XtNewString("Local");
	else
		username = XtNewString(hinfo[dirwin->host].username);
	temp = XtMalloc(strlen(hinfo[dirwin->host].hostname)+strlen(username)
		+strlen(dirwin->dirname)+8);
	sprintf(temp, "%s  (%s)  %s", hinfo[dirwin->host].hostname, username,
		dirwin->dirname);
	XtFree(username);
    XtVaSetValues(dirwin->w_shell, XmNtitle, temp, NULL);
	XtFree(temp);

	/* Update the "Go to Ancestors" menus */
	update_ancestors_menus(dirwin);

    /* Allocate space to hold directory entry info */
	for (i=0; i<dirwin->nentries; i++) {
		XtFree(dirwin->entries[i].name);
		XtFree(dirwin->entries[i].info);
	}
	if (dirwin->nentries)
		XtFree((char *)dirwin->entries);
	if (dlist->nentries)
		dirwin->entries = (struct entry_info *)XtMalloc(dlist->nentries*
			sizeof(struct entry_info));
	dirwin->nentries = dlist->nentries;

    /* Save information about directory entries */
    dirwin->max_entry_width = 0;
    for (i=0; i<dlist->nentries; i++) {
		einfo = &dirwin->entries[i];
		switch (dirwin->layout) {
		case TABULAR:
			einfo->name = XtNewString(dlist->entries[i]);
			einfo->type = UNKNOWN_TYPE;
			einfo->info = NULL;
	        width = XTextWidth(app_data.variable_width_layout_font,
				einfo->name, strlen(einfo->name))+2*TMARGIN;
			einfo->width = width;
			break;
		case ICONIC:
		case TREE:
			parse_short_entry(hinfo[host].system, dlist->entries[i],
				&einfo->name, &einfo->type);
			einfo->info = NULL;
	        width = XTextWidth(app_data.variable_width_layout_font,
				einfo->name, strlen(einfo->name))+2*TMARGIN+IWIDTH+IMARGIN;
			einfo->width = width;
			break;
		case FULL_INFO:
			parse_long_entry(hinfo[host].system, hinfo[host].server,
				dlist->entries[i], &einfo->name, &einfo->info, &einfo->type);
	        width = XTextWidth(app_data.fixed_width_layout_font,
				einfo->info, strlen(einfo->info))+2*TMARGIN;
			einfo->width = width;
			break;
		}
		einfo->state = UNSELECTED;
		einfo->indx = i;
		einfo->level = 0;
		einfo->dirwin = dirwin;
		einfo->expanded = False;
		dirwin->max_entry_width = MAX(dirwin->max_entry_width, width);
	}
	dirwin->entry_height = app_data.variable_width_layout_font->ascent+
		app_data.variable_width_layout_font->descent;
	switch (dirwin->layout) {
	case TABULAR:
	case FULL_INFO:
	    dirwin->entry_height += 2*TMARGIN;
		break;
	case ICONIC:
	case TREE:
	    dirwin->entry_height = MAX(dirwin->entry_height, IHEIGHT)+2*TMARGIN;
	}

    /* Update cache of referenced directories */
	add_to_history(DIRECTORY, hinfo[host].hostname, true_path);
	update_goto_menus_for_host(host);

	/* Perform geometry calculations for directory display */
    calc_dirwin_geometry(dirwin);

	/* Redraw changed part of directory display */
	if (need_to_create)
		reset_scrollbars(dirwin, 0, 0);
	else {
		if (anchor_rel_path) {
			set_first_visible_entry(dirwin, anchor_rel_path);
			XtFree(anchor_rel_path);
		} else {
			reset_scrollbars(dirwin, 0, 0);
			redraw_entire_dir(dirwin);
		}
	}

	/* Free up memory */
    XtFree(true_path);
	release_array_list(dlist);

	update_dir_controls();
	update_wins_menu();
	restore_prev_cursor();
	report_cache_use(dirwin, used_cache);
	return 0;
}


/*
 * close_directory_window - Removes the specified directory window and its
 *                          associated data structure.
 */
void
close_directory_window(dirwin)
struct dirwin_st *dirwin;
{
	struct dirwin_st *dwin;
	int i;
	int host = dirwin->host;

	/* If stop button is showing, get rid of it */
	if (stop_button_is_showing(dirwin))
		hide_stop_button();

	/* Remove dialog from cursor linked list */
	remove_dialog_from_list(dirwin->w_shell);

	/* Destroy window */
	XtDestroyWidget(dirwin->w_shell);

	/* Release associated memory */
	for (i=0; i<dirwin->nentries; i++) {
		XtFree(dirwin->entries[i].name);
		XtFree(dirwin->entries[i].info);
	}
	if (dirwin->nentries)
        XtFree((char *)dirwin->entries);
	XtFree(dirwin->dirname);

	/* Disconnect from linked list */
	if (dirwin->prev)
		dirwin->prev->next = dirwin->next;
	else
		dirwin_head = dirwin->next;
	if (dirwin->next)
		dirwin->next->prev = dirwin->prev;
	XtFree((char *)dirwin);

	/* Clean up if this is last directory window for remote host */
	dwin = dirwin_head;
	while (dwin && (dwin->host != host))
		dwin = dwin->next;
	if ((dwin == NULL) && (host != LOCAL)) {
		close(hinfo[host].ctrl_fd);
		release_host(host);
	}

	/* Update directory window controls */
	update_wins_menu();
	update_dir_controls();
}


void
clear_selected_entries()
{
	struct dirwin_st *dirwin;
	int i;

	dirwin = dirwin_head;
	while(dirwin) {
		if (dirwin->has_selection) {
			for (i=0; i<dirwin->nentries; i++)
				deselect_entry(&dirwin->entries[i]);
			dirwin->has_selection = False;
			return;
		}
		dirwin = dirwin->next;
	}
}


/*
 * has_selected_entries - Returns true if dirwin contains selected
 *                        entries, else False.
 */
has_selected_entries(dirwin)
struct dirwin_st *dirwin;
{
    int i;

    for (i=0; i<dirwin->nentries; i++)
        if (dirwin->entries[i].state == SELECTED)
            return True;
    
    return False;
}


/*
 * cb_refresh_dir - Callback to refresh a directory display.
 */
void
cb_refresh_dir(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
    struct dirwin_st *dirwin = (struct dirwin_st *)client_data;
	int retval;

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

    /* Clear error flag */
    raise_okflag();

	/* Make operation interruptable for remote hosts */
	if (dirwin->host != LOCAL)
		show_stop_button(dirwin);

	/* Make sure remote connection is good */
	if (dirwin->host != LOCAL) {
		retval = check_connection(dirwin->host, dirwin);
		if (retval < 0) {
			switch (retval) {
			case -6:
				record_abort("Refresh Directory Window");
				break;
			case -1:
				record_and_alert("Unable to refresh directory.",
					dirwin->w_shell);
			}
			hide_stop_button();
			end_op();
			return;
		}
	}

	/* Can assume directory is already displayed */
	retval = display_dir(dirwin->host, dirwin, dirwin->dirname, True, False,
		dirwin->cache_mode, False);
	switch (retval) {
	case -6:
		record_abort("Refresh Directory Window");
		break;
	case -3:
		restore_lost_connection(dirwin->host, dirwin);
		break;
	case -1:
		record_and_alert("Unable to refresh directory.", dirwin->w_shell);
	}
	if (dirwin->host != LOCAL)
		hide_stop_button();

	/* End operation */
	end_op();
}


/*
 * is_dir_displayed - Returns True if indicated directory is displayed,
 *                    else False.  If directory is found, "dirwin" is set
 *                    point to the directory window structure.  If the
 *                    directory is being displayed as a lower-level
 *                    subdirectory, "einfo", a pointer to the entry
 *                    structure for that directory is also returned
 *                    (otherwise einfo is set to NULL).  "path" must be
 *                    the full true path name (e.g., "/a/b/c").
 */
is_dir_displayed(host, path, dirwin, einfo)
int host;
char *path;
struct dirwin_st **dirwin;
struct entry_info **einfo;
{
	struct dirwin_st *dwin;
	struct entry_info *ret_einfo;
	int nlinks;
	char **links;
	char **parent_links;
	char **rel_path_links;
	char **ptr;
	char *parent_dir;
	char *rel_path;
	int system = hinfo[host].system;
	int i;
	int j;

	/* Does directory have its own display? */
	dwin = dirwin_head;
	while (dwin) {
		if (dwin->host == host && strcmp(dwin->dirname, path) == 0) {
			*dirwin = dwin;
			*einfo = NULL;
			return True;
		}
		dwin = dwin->next;
	}

	/* Is an ancestor being displayed?  Find the closest one. */
	links = path_to_links(system, path);
	nlinks = 0;
	ptr = links;
	while (*ptr++)
		nlinks++;
	for (i=nlinks-1; i>0; i--) {
		parent_links = (char **)XtMalloc(sizeof(char *)*(i+1));
		for (j=0; j<i; j++)
			parent_links[j] = XtNewString(links[j]);
		parent_links[i] = NULL;
		parent_dir = links_to_path(system, parent_links, i);
		release_path_links(parent_links);
		dwin = dirwin_head;
		while (dwin) {
			if (dwin->host == host && !strcmp(dwin->dirname, parent_dir)) {
				XtFree(parent_dir);
				goto found_ancestor;
			}
			dwin = dwin->next;
		}
		XtFree(parent_dir);
	}
	release_path_links(links);
	return False;

found_ancestor:

	/* Is directory being displayed as subdirectory of ancestor? */
	if (dwin->layout != TREE) {
		release_path_links(links);
		return False;
	}
	rel_path_links = (char **)XtMalloc(sizeof(char *)*(nlinks-i+1));
	for (j=0; j<nlinks-i; j++)
		rel_path_links[j] = XtNewString(links[i+j]);
	rel_path_links[nlinks-i] = NULL;
	rel_path = links_to_path(system, rel_path_links, nlinks-i);
	release_path_links(rel_path_links);
	release_path_links(links);
	ret_einfo = rel_path_to_entry(dwin, rel_path);
	XtFree(rel_path);
	if (ret_einfo && ret_einfo->expanded) {
		*dirwin = dwin;
		*einfo = ret_einfo;
		return True;
	} else
		return False;
}


/*
 * rel_path_to_entry - Return a pointer to the info for the entry
 *                     specifed by "path", a partial directory path.
 *                     Returns NULL if entry not found.
 */
struct entry_info *
rel_path_to_entry(dirwin, path)
struct dirwin_st *dirwin;
char *path;
{
	char **links;
	char **link_ptr;
	char *ptr;
	char *entry;
	int i;
	int level = 0;
	int system = hinfo[dirwin->host].system;

	if (dirwin->nentries == 0)
		return NULL;

	links = path_to_links(system, path);
	link_ptr = links;

	if (*link_ptr == NULL)
		fatal_error("Bug in rel_path_to_entry()");

	for (i=0; i<dirwin->nentries; i++) {
		if (dirwin->entries[i].level < level)
			break;
		entry = XtNewString(dirwin->entries[i].name);
		if ((system == SYS_VMS) && (ptr = strstr(entry, ".dir;")))
			*(ptr+4) = '\0';
		if ((dirwin->entries[i].level == level) && !strcmp(entry, *link_ptr)) {
			level++;
			link_ptr++;
			if (*link_ptr == NULL) {
				XtFree(entry);
				release_path_links(links);
				return &dirwin->entries[i];
			}
		}
		XtFree(entry);
	}

	release_path_links(links);
	return NULL;
}


/*
 * rel_path_to_nearest_entry - Return a pointer to the info for the entry
 *                             specifed by "path", a partial directory path.
 *                             If no such entry exists, return a pointer to
 *                             the info for the entry nearest to where the
 *                             entry for "path" would be.  Returns 0 if
 *                             there are no entries in the dirwin.
 */
struct entry_info *
rel_path_to_nearest_entry(dirwin, path)
struct dirwin_st *dirwin;
char *path;
{
	char *path_copy;
	char *link;
	int i;
	int level = 0;
	int retval;

	if (dirwin->nentries == 0)
		return NULL;

	path_copy = XtNewString(path);

	if ((link = strtok(path_copy, "/")) == NULL)
		fatal_error("Bug in rel_path_to_entry()");

	for (i=0; i<dirwin->nentries; i++) {
		if (dirwin->entries[i].level < level)
			break;
		if (dirwin->entries[i].level == level) {
			retval = strcmp(dirwin->entries[i].name, link);
			if (retval == 0) {
				level++;
				if ((link = strtok(NULL, "/")) == NULL)
					break;
			} else if (retval > 0)
				break;
		}
	}

	XtFree(path_copy);
	i = MIN(i, dirwin->nentries-1);
	return &dirwin->entries[i];
}


/*
 * set_first_visible_entry - In the specified directory window, position the
 *                           entry whose relative path is closest to the one
 *                           given on the first visible row.
 */
void
set_first_visible_entry(dirwin, rel_path)
struct dirwin_st *dirwin;
char *rel_path;
{
	struct entry_info *einfo;
	int row;
	int first_visible_row;

	/* Determine first visible row */
	if ((einfo = rel_path_to_nearest_entry(dirwin, rel_path))) {
		row = einfo->indx%dirwin->nrows;
		first_visible_row = 
			MIN(row, MAX(0, dirwin->nentries-dirwin->nrows_visible));
	} else
		first_visible_row = 0;

	/* Fix up scrollbars */
	reset_scrollbars(dirwin, -1, first_visible_row);

	/* Redraw visible part of directory display */
	redraw_entire_dir(dirwin);
}



syntax highlighted by Code2HTML, v. 0.9.1