/* This file is part of `fileSelect'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: FileSelect.c,v 2.1 1996/07/09 21:58:41 deaven Exp deaven $
 */

#if defined(sgi)
#define USE_OLD_REGEX
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <dirent.h>
#ifndef USE_OLD_REGEX
#include <regex.h>
#endif
#include <limits.h>
#include <X11/Xos.h>
#include <X11/keysym.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/List.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/AsciiText.h>

#include "FileSelectP.h"

#define NORMAL_BORDERWIDTH	1
#define ENHANCED_BORDERWIDTH	2

/* Select a file, accept additional children widgets.
 * The callback call_data contains a String giving the complete
 * path name currently selected, which may also be obtained
 * via the convenience function FSCompletePath().  The callback is
 * called when the user types <Return> in the file selection
 * area, or double-clicks on a filename (not yet XXX).
 *
 * Resources:               Type:          Defaults:
 * ----------------------   ------------   ------------------------
 * XtNdirectory           : String       : "."
 * XtNfilter              : String       : "*"
 * XtNfilename            : String       : ""
 * XtNverticalSpacing     : int          : 4
 * XtNborderWidth         : int          : 4
 * XtNcallback            : XtRCallback  : NULL
 */

#define PATH_SEPARATOR '/'

#define offset(field) XtOffset(FileSelectWidget, fileSelect.field)
static XtResource FileSelectResources[] = 
{
  {XtNdirectory, XtCDirectory, XtRString, sizeof(String),
    offset(directory), XtRString, "."},
  {XtNfilter, XtCFilter, XtRString, sizeof(String),
    offset(filter), XtRString, "*"},
  {XtNfilename, XtCFilename, XtRString, sizeof(String),
    offset(filename), XtRString, ""},
  {XtNborderWidth, XtCBorderWidth, XtRInt, sizeof(int),
    offset(borderWidth), XtRImmediate, (XtPointer) 4},
  {XtNverticalSpacing, XtCVerticalSpacing, XtRInt, sizeof(int),
    offset(verticalSpacing), XtRImmediate, (XtPointer) 4},
  {XtNcallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
    offset(callbacks), XtRPointer, NULL},
};
#undef offset

#define CORE  ((FileSelectWidget) fsw)->core
#define FILESELECT ((FileSelectWidget) fsw)->fileSelect
#define CURRENT ((FileSelectWidget) fsw_current)->fileSelect

static char *string_copy(char *s)
{
	unsigned int len = strlen(s);
	char *r = XtMalloc((len + 1) * sizeof(char));
	if(r) {
	  if(len > 0)
	    strcpy(r, s);
	  else
	    r[0] = '\0';
	}
	return r;
}

static char *string_append(char *s, char *append)
{
	unsigned int newlen =
	  (s ? strlen(s) : 0) + (append ? strlen(append) : 0) + 1;

	if(s) {
	  s = XtRealloc(s, newlen * sizeof(char));
	}
	else {
	  s = XtMalloc(newlen * sizeof(char));
	  if(s) *s = '\0';
	}

	if(s && append) strcat(s, append);

	return s;
}

static char **add_to_list(char **list, char *new)
{
	int i, n = 0;
	if(list) {
	  for(i=0; list[i]; i++) n++;
	  list = (char **) XtRealloc((XtPointer) list, (n + 2) * sizeof(*list));
	} else {
	  list = (char **) XtMalloc((n + 2) * sizeof(*list));
	}
	if(list) {
	  list[n++] = string_copy(new);
	  list[n++] = NULL;
	}
	return list;
}

static void delete_list(char **list)
{
	int i;
	if(list) {
	  for(i=0; list[i]; i++) XtFree(list[i]);
	  XtFree((XtPointer) list);
	}
}

static int cmp_files(const char **file1p, const char **file2p)
{
	const char *file1 = *file1p, *file2 = *file2p;
	int l1 = strlen(file1), l2 = strlen(file2);
	int dir1, dir2;
	dir1 = (l1 > 0 && file1[l1 - 1] == PATH_SEPARATOR ? 1 : 0);
	dir2 = (l2 > 0 && file2[l2 - 1] == PATH_SEPARATOR ? 1 : 0);
	if(dir1 != dir2) return (dir1 > dir2 ? -1 : 1);
	return strcmp(file1, file2);
}

/* Given a shell-style wildcard specification, generate an
 * extended POSIX regular expression to match filenames.
 */
static char *globpattern(char *s)
{
	int n;
	static char buf[256];

	buf[0] = '\0';
	while(*s != '\0') {
	  switch(*s) {
	    case '*':
	      strcat(buf, ".*");
	      break;

	    case '.':
	      strcat(buf, "\\.");
	      break;

	    default:
	      n = strlen(buf);
	      buf[n++] = *s;
	      buf[n++] = '\0';
	      break;
	  }
	  s++;
	}

	return buf;
}

/* Get the list of files, or NULL if the path
 * is not a valid directory.
 */
static char **get_files(char *path, char *filter)
{
	char **files = NULL;
	unsigned int num = 0;
	int status, useFilter = 1;
	unsigned int namelen;
	struct stat st;
	struct dirent *d;
	DIR *dirp;
	char name[MAXNAMLEN + 2], fullname[PATH_MAX], *pattern;
#ifndef USE_OLD_REGEX
	regex_t preg;
#endif
	if((dirp = opendir(path)) == NULL) {
	  files = add_to_list(files, "<invalid directory>");
	  return files;
	}

	pattern = globpattern(filter);

#ifdef USE_OLD_REGEX
	status = re_comp(pattern) ? 1 : 0;
#else
	status = regcomp(&preg, pattern, REG_EXTENDED | REG_NOSUB);
#endif
	if(status) useFilter = 0;

	while((d = readdir(dirp)) != NULL) {
	  namelen = strlen(d->d_name);
	  if(!(namelen == 1 && d->d_name[0] == '.')) {
	    strncpy(name, d->d_name, namelen);
	    name[namelen] = '\0';
	    sprintf(fullname, "%s/%s", path, name);
	    if(stat(fullname, &st) == 0) {
	      if(st.st_mode & S_IFDIR || (!useFilter) ||
#ifdef USE_OLD_REGEX
		  re_exec(name) == 1
#else
		  regexec(&preg, name, 0, NULL, 0) == 0
#endif
		)
	      {
		if(st.st_mode & S_IFDIR)
		  sprintf(name + namelen, "%c", PATH_SEPARATOR);
		files = add_to_list(files, name);
		num++;
	      }
	    }
	  }
	}
	closedir(dirp);

#ifndef USE_OLD_REGEX
	regfree(&preg);
#endif

	if(num > 1)
	  qsort(files, num, sizeof(*files), cmp_files);

	return files;
}

/* construct a string that gives a path spec from two others,
 * taking care to handle the slashes correctly.
 */
static char *addpath(char *p1, char *p2)
{
	int len;
	char *s = NULL;
	static char ps[2] = {PATH_SEPARATOR, '\0'};

	if(p1 && strlen(p1) > 0)
	  s = string_append(s, p1);

	if(p2 && strlen(p2) > 0) {
	  if(s && (len = strlen(s)) > 0 && s[len - 1] != PATH_SEPARATOR) s = string_append(s, ps);
	  s = string_append(s, p2);
	}

	if(s && (len = strlen(s)) > 1 && s[len - 1] == PATH_SEPARATOR) s[len - 1] = '\0';

	return s;
}

/* Concatenate two path specifications, returning an
 * absolute path.
 */
static char *catpath(char *basepath, char *relpath)
{
	char *path = NULL;
	char wdbuf[PATH_MAX + 1], swdbuf[PATH_MAX + 1];

	if(getcwd(swdbuf, PATH_MAX) == NULL)
	  perror("getting current directory");
	else {
	  if(relpath[0] == PATH_SEPARATOR)
	    path = string_copy(relpath);
	  else
	    path = addpath(basepath, relpath);

	  if(chdir(path) < 0)
	    perror("changing directory");
	  else {
	    if(getcwd(wdbuf, PATH_MAX) == NULL)
	      perror("getting new directory");
	    else {
	      XtFree(path);
	      path = string_copy(wdbuf);
	    }
	  }

	  chdir(swdbuf);
	}

	return path;
}

/* Handle a selection within the scrollable list of filenames.
 */
static void list_callback(Widget widget, XtPointer client_data, XtPointer call_data)
{
	Widget fsw = (Widget) client_data;
	XawListReturnStruct *l = (XawListReturnStruct *) call_data;
	struct stat st;
	char *path = addpath(FILESELECT.directory, l->string);

	if(stat(path, &st) >= 0)
	{
	  if(st.st_mode & S_IFDIR) {
	    char *old = FILESELECT.tmp.directory;
	    FILESELECT.tmp.directory = catpath(FILESELECT.directory, l->string);
	    XtVaSetValues(fsw, XtNdirectory, FILESELECT.tmp.directory, XtNfilename, "", NULL);
	    XtFree(old);
	  }
	  else {
	    XtVaSetValues(fsw, XtNfilename, l->string, NULL);
	  }
	}

	XtFree(path);
}

static void dir_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *dispatch)
{
	Widget fsw = (Widget) client_data;
	if(event->type == KeyPress) {
	  XKeyEvent *e = (XKeyEvent *) event;
	  if(XLookupKeysym(e, 0) == XK_Return) {
	    String sold = FILESELECT.tmp.directory3, ss;
	    XtVaGetValues(widget, XtNstring, &ss, NULL);
	    FILESELECT.tmp.directory3 = string_copy(ss);
	    XtVaSetValues(fsw, XtNdirectory, FILESELECT.tmp.directory3, NULL);
	    XtFree(sold);
	  }
	}
}

static void filter_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *dispatch)
{
	Widget fsw = (Widget) client_data;
	if(event->type == KeyPress) {
	  XKeyEvent *e = (XKeyEvent *) event;
	  if(XLookupKeysym(e, 0) == XK_Return) {
	    String sold = FILESELECT.tmp.filter, ss;
	    XtVaGetValues(widget, XtNstring, &ss, NULL);
	    FILESELECT.tmp.filter = string_copy(ss);
	    XtVaSetValues(fsw, XtNfilter, FILESELECT.tmp.filter, NULL);
	    XtFree(sold);
	  }
	}
}

/* This is the only place that we set a public value field outside
 * of SetValues().  It is broken because it's always one keystroke
 * behind the user... XXX
 */
static void select_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *dispatch)
{
	Widget fsw = (Widget) client_data;
	if(event->type == KeyPress) {
	  XKeyEvent *e = (XKeyEvent *) event;
	  XtVaGetValues(widget, XtNstring, &(FILESELECT.filename), NULL);
	  XawListUnhighlight(FILESELECT.listWidget);
	  if(XLookupKeysym(e, 0) == XK_Return)
	    XtCallCallbackList(fsw, FILESELECT.callbacks, FSCompletePath(fsw));
	}
}

static void layout(Widget fsw)
{
	Dimension width, height;
	Boolean ask = False;

	/* The width should ideally be as big as the larger of the 
	 * directory name or the list window.  We'll ask for 
	 * the larger of the two, plus a little extra.
	 */
	{ Dimension w1, w2;
	  XFontStruct *xfnt;
	  XtVaGetValues(FILESELECT.dirWidget, XtNfont, &xfnt, NULL);
	  w1 = XTextWidth(xfnt, FILESELECT.directory, strlen(FILESELECT.directory));
	  XtVaGetValues(FILESELECT.listWidget, XtNwidth, &w2, NULL);
	  width = (w1 > w2 ? w1 : w2) + 64 + 2 * FILESELECT.borderWidth + 
	    2 * ENHANCED_BORDERWIDTH;
	}
	if(width > FILESELECT.width) ask = True;

	/* The height should ideally be at least the default heights 
	 * of all the widgets except the list, and then some.
	 */
	{ Dimension h;
	  height = 2 * FILESELECT.borderWidth + FILESELECT.verticalSpacing +
	    6 * ENHANCED_BORDERWIDTH + 2 * NORMAL_BORDERWIDTH;
#define add(WIDGET) { \
  XtVaGetValues(WIDGET, XtNheight, &h, NULL); height += FILESELECT.verticalSpacing + h; }
	  add(FILESELECT.dirLabelWidget);
	  add(FILESELECT.dirWidget);
	  add(FILESELECT.filterWidget);
	  add(FILESELECT.selectWidget);
#undef add
	  height *= 3; /* the rest will be made up of the viewport */
	}
	if(height > FILESELECT.height) ask = True;

	/* Ask for a resize if we need to
	 */
	if(ask) {
	  /* Adjust the aspect ratio so it isn't displeasing to the eye.
	   */
	  if(height > 2 * width) width = height / 2;

	  /* If we've been asked for a size, just set it to what we
	   * want.  Otherwise, ask our parent for a resize.
	   */
	  if(CORE.width == 0 && CORE.height == 0) {
	    CORE.width = FILESELECT.width = width;
	    CORE.height = FILESELECT.height = height;
	  }
	  else {
	    XtGeometryResult r;
	    while((r = XtMakeResizeRequest(fsw, width, height,
	      &width, &height)) == XtGeometryAlmost);
	    if(r == XtGeometryYes) {
	      FILESELECT.width = width;
	      FILESELECT.height = height;
	    }
	  }
	}

	/* Lay out all of the widgets in turn.
	 */
	{ Dimension w, lheight, vheight, ycur = 0;
	  width = FILESELECT.width - 2 * (FILESELECT.borderWidth + ENHANCED_BORDERWIDTH);

	  XtVaGetValues(FILESELECT.dirLabelWidget, XtNheight, &lheight, NULL);

	  vheight = FILESELECT.height - 4 * lheight - 5 * FILESELECT.verticalSpacing
	    - 6 * ENHANCED_BORDERWIDTH - 2 * NORMAL_BORDERWIDTH;
	  if(vheight < 32) vheight = 32;

	  XtConfigureWidget(FILESELECT.dirLabelWidget,
	    FILESELECT.borderWidth, ycur, width, lheight, 0);

	  ycur += lheight + FILESELECT.verticalSpacing;

	  XtConfigureWidget(FILESELECT.dirWidget,
	    FILESELECT.borderWidth + ENHANCED_BORDERWIDTH, ycur,
	    width - 2 * ENHANCED_BORDERWIDTH, lheight, ENHANCED_BORDERWIDTH);

	  ycur += lheight + FILESELECT.verticalSpacing + 2 * ENHANCED_BORDERWIDTH;

	  XtVaGetValues(FILESELECT.filterLabelWidget, XtNwidth, &w, NULL);
	  XtConfigureWidget(FILESELECT.filterLabelWidget,
	    FILESELECT.borderWidth, ycur, w, lheight, 0);
	  XtConfigureWidget(FILESELECT.filterWidget,
	    w + FILESELECT.borderWidth + ENHANCED_BORDERWIDTH, ycur,
	    width - w - 2 * ENHANCED_BORDERWIDTH, lheight, ENHANCED_BORDERWIDTH);

	  ycur += lheight + FILESELECT.verticalSpacing + 2 * ENHANCED_BORDERWIDTH;

	  XtConfigureWidget(FILESELECT.viewportWidget,
	    FILESELECT.borderWidth + NORMAL_BORDERWIDTH, ycur,
	    width - 2 * NORMAL_BORDERWIDTH, vheight, NORMAL_BORDERWIDTH);

	  ycur += vheight + FILESELECT.verticalSpacing + 2 * NORMAL_BORDERWIDTH;

	  XtVaGetValues(FILESELECT.selectLabelWidget, XtNwidth, &w, NULL);
	  XtConfigureWidget(FILESELECT.selectLabelWidget,
	    FILESELECT.borderWidth, ycur, w, lheight, 0);
	  XtConfigureWidget(FILESELECT.selectWidget,
	    w + FILESELECT.borderWidth + ENHANCED_BORDERWIDTH, ycur,
	    width - w - 2 * ENHANCED_BORDERWIDTH, lheight, ENHANCED_BORDERWIDTH);
	}
}

static XtGeometryResult
GeometryManager(Widget fsw, XtWidgetGeometry *request, XtWidgetGeometry *reply)
{
	return(XtGeometryNo);
}

static void Resize(Widget fsw)
{
	FILESELECT.width = CORE.width;
	FILESELECT.height = CORE.height;
	layout(fsw);
}

static void Initialize(Widget request, Widget fsw, ArgList p_args, Cardinal *p_n)
{
	static XtTranslations trans = NULL;
	static XtAccelerators accel = NULL;
	static char *text_translations = 
	  "<Key>Delete: delete-previous-character()\n"
	  "<Key>BackSpace: delete-previous-character()\n"
	  "<Key>Left: backward-character()\n"
	  "<Key>Right: forward-character()\n"
	  "<Key>Return: no-op()\n"
	  "<Key>: insert-char()\n"
	  "<Btn1Down>: select-start()\n"
	  "<Btn1Motion>: extend-adjust()\n"
	  "<Btn1Up>:extend-end(PRIMARY, CUT_BUFFER0)\n"
	  "<Btn2Down>: insert-selection(PRIMARY, CUT_BUFFER0)\n"
	  ;
	static char *accel_translations =
	  "<Key>Return: select()\n"
	  ;

	if(trans == NULL)
	  trans = XtParseTranslationTable(text_translations);
	if(accel == NULL) 
	  accel = XtParseAcceleratorTable(accel_translations);

	XtVaSetValues(fsw, XtNaccelerators, accel, NULL);

	FILESELECT.tmp.directory = catpath(FILESELECT.directory, ".");
	FILESELECT.directory = FILESELECT.tmp.directory;

	FILESELECT.tmp.filename = NULL;
	FILESELECT.tmp.directory2 = NULL;
	FILESELECT.tmp.directory3 = NULL;
	FILESELECT.tmp.filter = NULL;

	FILESELECT.dirLabelWidget =
	  XtVaCreateManagedWidget("dirLabel", labelWidgetClass, fsw,
	    XtNlabel, "directory:",
	    XtNjustify, XtJustifyCenter,
	    NULL);

	FILESELECT.dirWidget =
	  XtVaCreateManagedWidget("dir", asciiTextWidgetClass, fsw,
	    XtNeditType, XawtextEdit,
	    XtNstring, FILESELECT.directory,
	    XtNresize, XawtextResizeWidth,
	    NULL);
	XtUninstallTranslations(FILESELECT.dirWidget);
	XtOverrideTranslations(FILESELECT.dirWidget, trans);
	XtInsertEventHandler(FILESELECT.dirWidget,
	  KeyPressMask, False, dir_handler, fsw, XtListTail);

	FILESELECT.filterLabelWidget =
	  XtVaCreateManagedWidget("filterLabel", labelWidgetClass, fsw,
	    XtNlabel, "filter:",
	    NULL);

	FILESELECT.filterWidget =
	  XtVaCreateManagedWidget("filter", asciiTextWidgetClass, fsw,
	    XtNstring, FILESELECT.filter,
	    XtNeditType, XawtextEdit,
	    NULL);
	XtUninstallTranslations(FILESELECT.filterWidget);
	XtOverrideTranslations(FILESELECT.filterWidget, trans);
	XtInsertEventHandler(FILESELECT.filterWidget,
	  KeyPressMask, False, filter_handler, fsw, XtListTail);

	FILESELECT.viewportWidget = 
	  XtVaCreateManagedWidget("viewport", viewportWidgetClass, fsw,
	    XtNallowHoriz, True,
	    XtNallowVert, True,
	    NULL);
	
	FILESELECT.files = get_files(FILESELECT.directory, FILESELECT.filter);

	FILESELECT.listWidget = 
	  XtVaCreateManagedWidget("list", listWidgetClass, FILESELECT.viewportWidget,
	    XtNforceColumns, True,
	    XtNdefaultColumns, 1,
	    XtNlist, FILESELECT.files,
	    NULL);
	XtAddCallback(FILESELECT.listWidget, XtNcallback, list_callback, fsw);
	XtInstallAccelerators(FILESELECT.listWidget, fsw);

	FILESELECT.selectLabelWidget =
	  XtVaCreateManagedWidget("selectLabel", labelWidgetClass, fsw,
	    XtNlabel, "selection:",
	    NULL);

	FILESELECT.selectWidget = 
	  XtVaCreateManagedWidget("select", asciiTextWidgetClass, fsw,
	    XtNstring, FILESELECT.filename,
	    XtNeditType, XawtextEdit,
	    NULL);
	XtUninstallTranslations(FILESELECT.selectWidget);
	XtOverrideTranslations(FILESELECT.selectWidget, trans);
	XtInsertEventHandler(FILESELECT.selectWidget,
	  KeyPressMask, False, select_handler, fsw, XtListTail);

	Resize(fsw);
}

static void Destroy(Widget fsw)
{
	delete_list(FILESELECT.files);
	XtFree(FILESELECT.tmp.filename);
	XtFree(FILESELECT.tmp.directory);
	XtFree(FILESELECT.tmp.directory2);
	XtFree(FILESELECT.tmp.directory3);
	XtFree(FILESELECT.tmp.filter);
}

static int nstrcmp(String s1, String s2)
{
	if(s1 == NULL) {
	  if(s2 == NULL) return 0;
	  return -1;
	}
	else if(s2 == NULL)
	  return 1;

	return strcmp(s1, s2);
}

static void Rescan(Widget fsw)
{
	char **old = FILESELECT.files;
	XtVaSetValues(FILESELECT.dirWidget, XtNstring, FILESELECT.directory, NULL);
	FILESELECT.files = get_files(FILESELECT.directory, FILESELECT.filter);
	XawListChange(FILESELECT.listWidget, FILESELECT.files, 0, 0, True);
	delete_list(old);
	layout(fsw);
}

static Boolean SetValues(Widget fsw_current,
	Widget request, Widget fsw, ArgList args, Cardinal *n)
{
	Boolean redisplay = False, rescan = False;
	String oldfn = NULL, olddir = NULL;

	if(nstrcmp(FILESELECT.directory, CURRENT.directory)) {
	  int len;
	  if(FILESELECT.directory &&
	     (len = strlen(FILESELECT.directory)) > 1 &&
	     FILESELECT.directory[len - 1] == PATH_SEPARATOR)
	  {
	    FILESELECT.directory[--len] = '\0';
	  }
	  rescan = True;
	}

	if(nstrcmp(FILESELECT.filename, CURRENT.filename)) {
	  /* If there are path separators in this string, we need to set the directory,
	   * too.
	   */
	  char *p;
	  if(FILESELECT.filename && (p = strrchr(FILESELECT.filename, PATH_SEPARATOR)))
	  {
	    String dir = XtNewString(FILESELECT.filename);
	    olddir = FILESELECT.tmp.directory2;

	    *strrchr(dir, PATH_SEPARATOR) = '\0';

	    if(dir[0] == PATH_SEPARATOR) /* absolute path spec */
	    {
	      FILESELECT.tmp.directory2 = XtNewString(dir);
	    }
	    else  /* relative path name */
	    {
	      char buf[PATH_MAX];
	      sprintf(buf, "%s%c%s", FILESELECT.directory, PATH_SEPARATOR, dir);
	      FILESELECT.tmp.directory2 = XtNewString(buf);
	    }
	    FILESELECT.directory = FILESELECT.tmp.directory2;

	    oldfn = FILESELECT.tmp.filename;
	    FILESELECT.tmp.filename = XtNewString(p + 1);
	    FILESELECT.filename = FILESELECT.tmp.filename;

	    rescan = True;
	  }
	  XtVaSetValues(FILESELECT.selectWidget, XtNstring, FILESELECT.filename, NULL);
	}

	if(nstrcmp(FILESELECT.filter, CURRENT.filter))
	{
	  XtVaSetValues(FILESELECT.filterWidget, XtNstring, FILESELECT.filter, NULL);
	  rescan = True;
	}

	if(rescan) Rescan(fsw);

	XtFree(olddir);
	XtFree(oldfn);

	return redisplay;
}

String FSCompletePath(Widget fsw)
{
	static String s = NULL;

	if(XtIsFileSelect(fsw)) {
	  String sel;
	  XtVaGetValues(FILESELECT.selectWidget, XtNstring, &sel, NULL);
	  if(sel != FILESELECT.filename)
	    FILESELECT.filename = sel;
	  XtFree(s);
	  s = addpath(FILESELECT.directory, FILESELECT.filename);
	}

	return s;
}

static void fsw_select(Widget fsw, XEvent *event, String *argv, Cardinal *argc)
{
	if(!XtIsFileSelect(fsw)) return;
	XtCallCallbackList(fsw, FILESELECT.callbacks, FSCompletePath(fsw));
}

static void fsw_rescan(Widget fsw, XEvent *event, String *argv, Cardinal *argc)
{
	if(!XtIsFileSelect(fsw)) return;
	Rescan(fsw);
}

static XtActionsRec actions[] = 
{
	{"select", fsw_select},
	{"rescan", fsw_rescan}
};

static char translations[] =
 "<Btn1Down>: rescan()\n"
 "<Key>Return: rescan()\n"
 ;

/* Complete class definition ---------------------------------- */

struct FileSelectClassRec_s fileSelectClassRec = 
{
  {        /* CoreClassPart */
    (WidgetClass) &compositeClassRec,  /* superclass */
    "FileSelect",                  /* class_name */
    sizeof(FileSelectRec),         /* widget_size */
    NULL,                          /* class_initialize */
    NULL,                          /* class_part_initialize */
    False,                         /* class_init */
    Initialize,                    /* initialize */
    NULL,                          /* initialize_hook */
    XtInheritRealize,              /* realize */
    actions,			   /* actions */
    XtNumber(actions),		   /* num_actions */
    FileSelectResources,           /* resources */
    XtNumber(FileSelectResources), /* num_resources */
    NULLQUARK,                     /* xrm_class */
    True,                          /* compress_motion */
    True,                          /* compress_exposure */
    True,                          /* compress_enterleave */
    True,                          /* visible_intrest */
    Destroy,                       /* destroy */
    Resize,                        /* resize */
    XtInheritExpose,               /* expose */
    SetValues,                     /* set_values */
    NULL,                          /* set_values_hook */
    XtInheritSetValuesAlmost,      /* set_values_almost */
    NULL,                          /* get_values_hook */
    NULL,                          /* accept_focus */
    XtVersion,                     /* version */
    NULL,                          /* callback_private */
    translations,		   /* tm_translations */
    XtInheritQueryGeometry,	   /* query_geometry */
    XtInheritDisplayAccelerator,   /* display_accelerator */
    NULL			   /* extension */
  },
  { 	/* CompositeClassPart */
    GeometryManager,		   /* geometry_manager */
    XtInheritChangeManaged,	   /* change_managed */
    XtInheritInsertChild,	   /* insert_child */
    XtInheritDeleteChild,	   /* delete_child */
    NULL			   /* extension */
  },
};

WidgetClass fileSelectWidgetClass = (WidgetClass) &fileSelectClassRec;

