/*****************************************************************************
 * Controller.m
 * 
 * Authors: 
 * 	Denise Howard
 * 	howardd@swissbank.com
 *
 *	 Michael Hawley
 *	 mike@media.mit.edu
 *
 * Copyright (c) MIT Media Laboratory, 1991-1995.
 *
 * Description:
 *	This is the app delegate for Opener.
 *
 * Revisions:
 *	See Revision_History.rtfd in project directory HelpOpener.
 *
 * To add new filetypes:
 *	See Modifying.rtfd in project directory HelpOpener.
 *
 * Modified by Tsutomu_Eguchi@ns.saga-med.ac.jp	  April 1998
 *	Add bzip2, bunzip2, binhex support
 *	See Modifying.rtfd in project directory HelpOpener.
 *
 *****************************************************************************/

#import "Controller.h"
#import "Help.h"
#import "ComposeSuggestion.h"

@interface Speaker(ObsoleteMethods)
- (int)registerWindow:(int)windowNum toPort:(port_t)aPort;
- (int)unregisterWindow:(int)windowNum;
@end

@implementation Controller

static NXDefaultsVector OpenerDefaults = {
    {"TmpDir",  "/tmp"},
    {"ArchiveFormat",  ".tar.Z"},
    {"FirstUsed",  ""},
    {NULL}
};    

#define MaxLen 256
#define MaxSuf 64
#define MaxTab 64
#define MaxPend 512

#define LS_MSG_YES NXLocalizedString("Yes", NULL, "LS_MSG_YES") //TE
#define LS_MSG_NO NXLocalizedString("No", NULL, "LS_MSG_NO") //TE
#define LS_MSG_COULDNT_OPEN \
 NXLocalizedString("Couldn't open file: %s",NULL,"LS_MSG_COULDNT_OPEN") //TE

char TmpDir[1024] = "/tmp";
char *iconPathList = (char *)0;
char MoreFiles[8192] = "";
int Reset = 1;

char Suffix[MaxTab][MaxSuf],
     Command[MaxTab][MaxLen];
int NT = 0;

char PSuffix[MaxTab][MaxSuf],
     PCommand[MaxTab][MaxLen];
int PNT = 0;

char *Pending[MaxPend];
int NP = 0;

static id me, pb2;
static char Files[8192];

/*****************************************************************************/
/* Functions start here                                                      */
/*****************************************************************************/

int 
err(a,b,c,d) 
	char *a,*b,*c,*d;
{
	char t[1024];
	extern id NXApp;
	sprintf(t,a,b,c,d);
	return NXRunAlertPanel([NXApp appName],t,0,0,0)==1;
}

char *
tmp(char *s,  char *suffix, int isDir) 
{
/* 
 * return a path for a temporary file
 * named "/tmp/O_[n_]file", where 'file' is 's';
 * e.g., /a/b/foo.bar, .bar => /tmp/O_foo
 * if 'isDir', map a "." in the suffix to "_" to ensure that
 * the directory wrapper will open properly.
 */
	static char t[1024];
	char *p, u[1024];
	int k = 1;
	
	strcpy(u,s);
	if (*suffix) u[strlen(u) - strlen(suffix)] = '\0';
	if (p = rindex(u,'/')) p++;
	else p = s;
	if (isDir) {
		char *x = rindex(p,'.');
		if (x) *x = '_';
	}
	sprintf(t,"%s/O_%s", TmpDir, p);
	while (access(t,0)==0) sprintf(t, "%s/O_%d_%s", TmpDir, k++, p);
	return t;
}

char *
path(char *s)
/*
 * if 's' is in [NXArgv[0]], return [NXArgv[0]]/s.
 * (for looking up internal commands and files in .../Opener.app/...).
 */
{
	char t[1024], *q, *r;
	static char p[1024];
	extern char **NXArgv;

	*t = '\0';
	sscanf(s,"%s",t);
	if (!*t || *t=='(') return s;
	r = s + strlen(t);
	strcpy(p,*NXArgv);
	if (q=rindex(p,'/'))
		strcpy(q+1,t);
	else
		strcpy(p,t);
	if (access(p,0)==0){
		strcpy(p+strlen(p),r);
		return p;
	}
	return s;
}

char *
skipsp(char *s) 
{
	while (*s==' ' || *s=='\t' || *s == '\n') ++s;
	return s;
}

int
blank(char *s) 
{ /* true if 's' is blank */
	while (*s == ' ' || *s=='\t' || *s == '\n') ++s;
	return !*s;
}

char *
save(char *s) 
{
	char *p = (char *)malloc(strlen(s)+1);
	if (p) strcpy(p,s);
	return p;
}

void
stripcomment(char *s) 
{ /* truncate 's' at a comment character ('#') */
	char *p = index(s,'#');
	if (p && (p==s || p[-1] != '\\')) *p = '\0';
}

char *
strindex(char *s, char *t) 
{ /* return ptr to first match of 't' in 's' */
	int n = strlen(t);
	
	if (s)
		while (*s)
			if (!strncmp(s, t, n)) return s;
			else s++;
	return (char *)0;
}

// Case-insensitive version of equal().  
// Commented out by Denise Howard 08/93 Opener 3.1.1.
// int 
// equal(char *s, char *t) 
// { /* true if 's' & 't' are equal (case-insens.) */
//	char a, b;
//	while (*s && *t){
//		a = isupper(*s)? tolower(*s) : *s;
//		b = isupper(*t)? tolower(*t) : *t;
//		s++, t++;
//		if (a != b) return 0;
//	}
//	return  (*s || *t)? 0 : 1;
// }

/* Case-sensitive version of equal(). */
/* Added by Howard 08/93 Opener 3.1.1. */
int 
equal(char *s, char *t) 
{ /* true if 's' & 't' are equal (case-sens.) */
	char a, b;
	while (*s && *t){
		a = *s;
		b = *t;
		s++, t++;
		if (a != b) return 0;
	}
	return  (*s || *t)? 0 : 1;
}

int 
tailmatch(char *s, char *t)
{ /* true if t appears at end of s */
	int tn = strlen(t), sn = strlen(s);
	if (tn > sn) return 0;
	return equal(s+(sn-tn), t);
}

char *
shstrcat(char *s, char *t)
{ // strcat, but quote shell metachars
	register char *p;
	
	while (*s) s++;
	p=t;
	if (*p=='~') *s++='\\';	// csh "feature"
	while (*p) {
		if (*p<=' '||*p>'~'||index(";&()|<>\\'\"$*?[]^!", *p)) *s++='\\';
		*s++=*p++;
	}
	*s='\0';
	return t;
}

void
subfname(char *s, char *a, char *b) 
{ /* like 'substr', but assumes the "b" string is a filename that may need
	special-to-shell characters quoted */

	char q[8192], quoted_b[2048];
	char *p = s;
	int n = strlen(a);
	quoted_b[0] = '\0';
	shstrcat(quoted_b, b);
	for (;*p;p++){
		if (*p == *a && strncmp(p,a,n)==0){
			strcpy(q,p+n);
			strcpy(p,quoted_b);
			strcpy(p+strlen(quoted_b),q);
			p += strlen(quoted_b)-1;
		}
	}
}

int 
fdTime(int f)
{ /* return size of file 'f' */
	struct stat b;
	fstat(f, &b);
	return b.st_mtime;
}

void
clearPackItems() 
{
	int i, n = [[pb2 target] count];
	for (i=0;i<n;i++){
		[[pb2 target] removeItemAt:i];
	}
}

void
addPackItem(char *s) 
{
// What this function does:  add a cell to the pb2 matrix with title 's',
// and assign it a selector so that when someone selects it the title
// becomes the new default pack suffix.
	id c;
	c = [[pb2 target] addItem:s];
	[c setTarget:me]; 
	[c setAction:@selector(setPackSuffix:)];
}

void
resetPackItems() 
{
	int i;
	clearPackItems();
	for (i=0;i<PNT;i++)
		addPackItem(PSuffix[i]);
}

void
addUnpackItem(char *s) 
{
	id c;
	c = [[pb2 target] addItem:s];
	[c setTarget:me]; 
	[c setAction:@selector(setUnpackSuffix:)];
}

void
loadTable() 
{
/*
 * Unpacking files is table-driven.
 * The file "Opener.table" is of the form
 *    suffix command
 * like
 *    .Z	uncompress < $f > $t
 * When opening files, the table is checked to
 * find a matching command for the given suffix.
 */
	char s[1024];
	FILE *f;
	static int first = 1;
	static int t = 0;
	int pack = 0;
	
	strcpy(s,path("Opener.table"));
	if (!first){
		if (f=fopen(s,"r")){
			int tt = fdTime(fileno(f));
			fclose(f);
			if (tt == t) return;
			t = tt; 				// table has changed -- reread it
			NT=PNT=0;
			clearPackItems();
			printf("rereading table\n");
		} else
			return;
	}
	first = 0;
	if (f = fopen(s,"r")){
		char *p, *q;
		t = fdTime(fileno(f));
		while (fgets(s,sizeof s,f)){
			stripcomment(s);
			if (blank(s)) continue;
			if (strncmp(s,"Unpack:",7)==0) continue;
			if (strncmp(s,"Pack:",5)==0){ pack++; continue; }
			if (pack){
				for (p=skipsp(s), q=PSuffix[PNT]; 
					*p && *p != ' ' && *p != '\t' && (q-PSuffix[PNT])<(MaxSuf-1);
					*q++ = *p++) ;
				*q = '\0';
				for (p = skipsp(p), q=PCommand[PNT];
					*p && (*p != '\n') && (q-PCommand[PNT])<(MaxLen-1);
					*q++ = *p++) ;
				*q='\0';
				addPackItem(PSuffix[PNT]);
// printf("[%s]: %s\n",PSuffix[PNT],PCommand[PNT]);
				if (!blank(PSuffix[PNT])&& !blank(PCommand[PNT]) && PNT < MaxTab)
					++PNT;
			} else {
				for (p=skipsp(s), q=Suffix[NT]; 
					*p && *p != ' ' && *p != '\t' && (q-Suffix[NT])<(MaxSuf-1);
					*q++ = *p++) ;
				*q = '\0';
				for (p = skipsp(p), q=Command[NT];
					*p && (*p != '\n') && (q-Command[NT])<(MaxLen-1);
					*q++ = *p++) ;
				*q='\0';
// printf("[%s]: %s\n",Suffix[NT],Command[NT]);
				if (!blank(Suffix[NT]) && !blank(Command[NT]) && NT < MaxTab)
					++NT;
			}    
		}
		fclose(f);
	}
}

int
findSuffix(char *s) 
{
	char *p = s + strlen(s);
	int i;
	loadTable();
	for (i=0;i<NT;i++)
		if (equal(Suffix[i],p-strlen(Suffix[i])))
			return i;
	return -1;
}

int
findPSuffix(char *s) 
{
	char *p = s + strlen(s);
	int i;
	loadTable();
	for (i=0;i<PNT;i++)
		if (equal(PSuffix[i],p-strlen(PSuffix[i]))) 
			return i;
	return -1;
}

void
ex_unpack(char *s) 
{
	char t[1024];
	if (strncmp(s,"unpack: ",8)) return;
	strcpy(t,skipsp(s+8));
	strcpy(s,"mkdir $t; cd $t; "); strcat(s,t);
}

void
substr(char *s, char *a, char *b) 
{ /* like 'sub', but with strings */
	char q[8192];
	char *p = s;
	int n = strlen(a);
	for (;*p;p++){
		if (*p == *a && strncmp(p,a,n)==0){
			strcpy(q,p+n);
			strcpy(p,b);
			strcpy(p+strlen(b),q);
			p += strlen(b)-1;
		}
	}
}

void
expand(char *s, int i, char *c) 
{
/*
 * 's' is the current filename.
 * 'i' is the index in the Suffix/Command table.
 * 'c' is the command to fill;
 * Replace "unpack: ..." with the standard unpack command
 *         $f with the filename, 
 *         $g   => $f without the path
 *         $r   => $f without any extensions
 *         $R   => $f without any extensions and without the path
 *         $e   => all the extensions (including the dot)
 *         $t with the temp
 */
	char *T, *p, S[MaxSuf], q[1024], *r;
	int isDir = 0;
	
	strcpy(c,Command[i]);
	strcpy(S,s+strlen(s)-strlen(Suffix[i]));
	if (strindex(c,"mkdir $t") || strindex(c,"unpack: ")) isDir = 1;
	T = tmp(s,Suffix[i],isDir); 
	
	ex_unpack(c);     /* rewrite "unpack: ..." */
	
	p = c;	      /* prepend the $p/... paths */
	while (p = strindex(p,"$p/")){
		sscanf(p,"%s",q);
		r = path(q+3);
		substr(p,q,r);
	}
	
	if (!strindex(c,"open ")){
		if (isDir) strcat(c,"; (cd $t; open . )"); // make sure directory opens
		else strcat(c,"; open $t");
	}
	
	subfname(c,"$f",s); /* expand the abbreviations */
	subfname(c,"$t",T);
	subfname(c,"$s",S);
    /*******************************************************/
    /*   The following chunk handles the $r abbreviation   */
    /*******************************************************/
    {
    	char root[1024];
        int index=0;
        while(s[index]!='\0' && index<1024 && s[index]!='.'){
			root[index] = s[index];
			index++;
		}
		root[index]='\0';
 		subfname(c,"$r",root);
	}
	/*******************************************************/
	/*   The following chunk handles the $e abbreviation   */
	/*******************************************************/
	{
		char ext[1024];
		int index=0,ptr=0;
		ext[0]='\0';
		while(s[index]!='\0' && index<1024 && s[index]!='.'){
			index++;
		}
		if(s[index]=='.'){
			ptr = index;
			while(s[index]!='\0' && index<1024){
			ext[index-ptr]=s[index];                    index++;
			}
			ext[index-ptr]='\0';
		}
		subfname(c,"$e",ext);
	}
	/*******************************************************/
	/*   The following chunk handles the $R abbreviation   */
	/*******************************************************/
	{
		char rroot[1024];
		int index=0;
		rroot[0]='\0';
		while(s[index]!='\0'){index++;}
		while(index>-1 && s[index]!='/'){index--;}
		if(s[index]=='/'){
			index++;
			while(s[index]!='\0' && index<1024 && s[index]!='.'){
				rroot[index] = s[index];
				index++;
			}
		}
		subfname(c,"$R",rroot);
	}
	/*******************************************************/
	/*   The following chunk handles the $g abbreviation   */
	/*******************************************************/
	{
		char sroot[1024];
		int index=0;
		sroot[0]='\0';
		while(s[index]!='\0'){index++;}
		while(index>-1 && s[index]!='/'){index--;}
		if(s[index]=='/'){
			index++;
			while(s[index]!='\0' && index<1024){
				sroot[index] = s[index];
				index++;
			}
		}
		subfname(c,"$g",sroot);
	}
}

void
getTmpName(char *s, char *t) 
{
/*
 * Copy a /tmp name for the comp.sources archive into 't'.
 * if 's' is ...comp.sources/.../foo/part00.Z ==> /tmp/O_foo
 *           ...comp.sources/.../foo.Z        ==> /tmp/O_foo
 */
	char *p = rindex(s,'/'), q[1024], c;
	int n;

	if (!p) return (void)strcpy(t,s);
	strcpy(q,p+1);
	c = q[4]; q[4]='\0';

	if (equal(q,"part") && isdigit(c)){ /* skip to previous */
		*p='\0';
		p = rindex(s,'/');
		if (!p) p=s;
		else p++;
	} else p++;

	strcpy(t,p);
	n = findSuffix(t);
	p = (n== -1)? ".Z" : Suffix[n];
	if (t[strlen(t)-strlen(p)]=='.')
		t[strlen(t)-strlen(p)] = '\0';
	strcpy(t,tmp(t,p,0));
}

int 
System(fmt, a,b,c,d,e,f,g) 
	char *fmt,*a,*b,*c,*d,*e,*f,*g;
{
	char t[8192];
	sprintf(t,fmt,a,b,c,d,e,f,g);
	printf("! %s\n",t);
	return system(t);
}

void 
openFile(char *s)
{ /* open 's' in workspace */
	int ok = 0;
	id p = [NXApp appSpeaker];
	
	[p setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[p openTempFile:s ok:&ok];
	if (!ok) err(LS_MSG_COULDNT_OPEN,s);
}

void 
previewFile(char *s) 
{ /* open 's' in the default PostScript previewer */
/* This function worked over for 3.1.1 by Howard; it never worked before
 * but it does now. */
	char *a = (char *)0; 
	char *index;
	char head[1024];
	int ok = 0;
	id p = [NXApp appSpeaker];
	
	/* get handle on the user's favorite Preview application */
	if (!a) a = "Preview";
	[p setSendPort:NXPortFromName(a, NULL)];
	
	getTmpName(s, head);
	index = rindex(&head[0], '.');	
	if (tailmatch(s, ".PS"))
		strcpy(index+1, "ps");
	else
		strcpy(index+1, "eps");
	
	System("cp %s %s", s, head);
	[p openFile: head ok:&ok];
	if (!ok) err(LS_MSG_COULDNT_OPEN,s);
}

int 
confirm(a,b,c,d) 
	char *a,*b,*c,*d;
{
	char t[1024];
	extern id NXApp;
	sprintf(t,a,b,c,d);
	return NXRunAlertPanel([NXApp appName],t,LS_MSG_YES,LS_MSG_NO,NULL)==1; //TE
}

int
unpackFile(char *s) 
{
	int i;
	char c[2048];
	i = findSuffix(s);
	if (i != -1){         /* we found a suffix and command in the table */
		expand(s,i,c);
		[me system:c];    // System("(%s)&",c);
	} else
	if (tailmatch(s,".PS" ) || tailmatch(s,".EPS")) /* catch some PS files */
		previewFile(s);
	else
		err("Couldn't open '%s'",s);
	return 1;
}

int 
isCompSourceArchive(char *s)
{
	return ((strindex(s,"comp.sources") || strindex(s,"mod.sources") ||
				strindex(s,"net.sources")) &&
		equal(s+strlen(s)-2,".z") &&
		!strindex(s,"index") && !strindex(s,"Index") && !strindex(s,"INDEX") &&
		!strindex(s,"files") && !strindex(s,"Files") && !strindex(s,"FILES"));
}

void 
reap(DPSTimedEntry n, double now, char *userData)
{
	int i, np;
	char x[1024], head[1024], c[8192], *p, q[1024], *r;
	
	DPSRemoveTimedEntry(n);
	np = NP;
	if (!NP) return;
	NP = 0;
	
	if (confirm("Try to build %s...?",Pending[0])){
		strcpy(x,Pending[0]);
		getTmpName(x,head);
		sprintf(c,"(mkdir $t; cp ");
		for (i=0; i<np; i++) 
			strcat(c,Pending[i]), strcat(c," ");
		sprintf(c+strlen(c)," $t; $p/builder $t; open $t $t/errors)&");
	
		p = c;	      /* prepend the $p/... paths */
		while (p = strindex(p,"$p/")){
			sscanf(p,"%s",q);
			r = path(q+3);
			substr(p,q,r);
		}
		substr(c,"$t",head);
		System("%s",c);
	} else {
		for (i=0;i<np;i++) 
			unpackFile(Pending[i]);
	}
	for (i=0;i<np;i++) 
		free(Pending[i]);
}

void
pending(char *s)
{
/*
 * One or more files from a comp.sources archive are being selected.
 * Put them on a queue -- reap() will unpack them specially.
 */
	if (NP == MaxPend)
		return (void)err("Too many pending files!  Try quitting.");
	Pending[NP++] = save(s);
	DPSAddTimedEntry(4.0, (DPSTimedEntryProc)reap, 0, 10);
}

void
stot(char *s, char **t, char c) 
{ /* split 's' into a table 't' */
	char *p;
	while (p = index(s,c)){
		*p++ = '\0';
		*t++ = s;
		s = p;
	}
	*t++ = s;
	*t = (char *)0;
}

void
trimdir(char *d, char **t) 
{ /* put longest leading directory of t into d */
	int i, n;
	char *p,*q;
	
	*d = '\0';
	p = rindex(t[0],'/');
	if (p){
		*p='\0';
		strcpy(d,t[0]); strcat(d,"/");
		*p='/';
	} else return ;

	if (t[1]){
		for (i=0;t[i];i++){
			for (p=d,q=t[i]; *p && *q && *p == *q; p++, q++);
			if (!*p) continue;
			*p = '\0';
		}
	}
	n = strlen(d);
	for (i=0;t[i];i++) t[i] += n;
}

void
pexpand(i,c,d,f,F,t)
    char *c, *d, *f, *F, *t;
    int i;
/*
 * like expand, but for packing:
 * 'i' is the index in the Suffix/Command table.
 * 'c' is the command to fill;
 * 'd' is the prefix directory
 * 'F' is the files without 'd' removed
 * 'f' is the files with 'd' removed
 * 't' is the files with 'd' removed
 */
{
	char *p, q[1024], *r; 
	strcpy(c,PCommand[i]);
	
	p = c;	      /* prepend the $p/... paths */
	while (p = strindex(p,"$p/")){
		sscanf(p,"%s",q);
		r = path(q+3);
		substr(p,q,r);
	}
	substr(c,"$F",F);
	substr(c,"$f",f);
	substr(c,"$d",d);
	substr(c,"$t",t);
}


void
expand_files(char *s, char *p, char *dir) 
{
	char *t[1024], x[8192];
	int i;
	strcpy(x,s);
	stot(x,t,'\t');
	if (dir) trimdir(dir,t);
	*p = '\0';
	for (i=0;t[i];i++){
		if (i > 0) strcat(p," ");
		shstrcat(p,t[i]);
		p += strlen(p);
	}
}

char *
getText(id t, char *s) 
{
	id d = [t docView];
	int n = [d textLength];
	char *p;
	*s = '\0';
	[d getSubstring:s start:0 length:[d textLength]];
	s[n] = '\0';
	for (p=s; *p; p++) if (*p == '\n') *p = ' ';
	return s;
}

void
setText(id t, char *s) 
{
	id w = [t window], d = [t docView];
	static NXPoint origin = {0.0,0.0};
	[w disableFlushWindow];
	[d setMonoFont:YES];
	[d setText:s];
	[d setSel:0:999999];
	[d setSelGray:0.0];
	[d selectNull];
	[d scrollPoint:&origin];
	[t display];
	[[w reenableFlushWindow] flushWindow];
	NXPing();
}

int
setSuffixes(char *s) 
{
	char *p = s + strlen(s);
	int i, r = -1;
	loadTable();
	clearPackItems();
	for (i=0;i<NT;i++)
		if (equal(Suffix[i],p-strlen(Suffix[i]))){
			if (r == -1) r = i;
			addUnpackItem(Suffix[i]);
		}
	if (r >= 0) [[pb2 selectedCell] setTitle:Suffix[r]];
	return r;
}

/*****************************************************************************/
/* Action methods start here                                                 */
/*****************************************************************************/

- appWillInit:sender 
{
	me = self;
	return self;
}

- appDidInit:sender 
{
//	char subj[512] = "Opener-";
	unsigned int wn;
	id s = [NXApp appSpeaker];
	
	NXRegisterDefaults("Opener", OpenerDefaults);
	strcpy(TmpDir, ((char *)NXGetDefaultValue("Opener", "TmpDir")));	
	[tmpDirTF setStringValue:TmpDir];
	[versionTF setStringValue:[self version]];

//  This new-user monitoring feature now obsolete (2/95) DJH
//	strcat(subj, [self version]);
//	strcat(subj, "-newuser");
//	OpenerMonitor(subj);
		
	NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
	[s setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[s registerWindow:wn toPort:[[NXApp appListener] listenPort]];

	pb2 = packButton2;
	return self;
}

- appWillTerminate:sender 
{
	unsigned int wn;
	
	NXConvertWinNumToGlobal([[NXApp appIcon] windowNum], &wn);
	[self removeTmpFiles:sender];
	[[NXApp appSpeaker] unregisterWindow:wn];
	[helpObject free];
	return self;
}

- (BOOL)appAcceptsAnotherFile:sender 
{
	return YES;
}

#define LS_MSG_REMOVE_SCRATCH_FILE \
NXLocalizedString("Remove the scratch files in %s?",NULL,"LS_MSG_REMOVE_SCRATCH_FILE") //TE

- removeTmpFiles:sender 
{
	if ([self scratchFiles] && 
		confirm(LS_MSG_REMOVE_SCRATCH_FILE, TmpDir)) { //TE
		if (strcmp(TmpDir,"/tmp"))
			System("rm -rf /tmp/O_* %s/O_* &", TmpDir);
		else
			System("rm -rf /tmp/O_* &");
	}
	return self;
}

- setTmp:sender 
{
	char *s = (char *)[sender stringValueAt:0];
	if (!s || !*s || access(s,0)){
		if (s && *s) err("Couldn't access '%s'",s);
		s = (char *)[sender stringValueAt:0];
		NXWriteDefault("Opener", "TmpDir", s);
		strcpy(TmpDir, s);
		[tmpDirTF setStringValue:s];
	}
	return self;
}

#define LS_MSG_UNPACK_FILES_IN \
NXLocalizedString("Unpack files in:", NULL, "LS_MSG_UNPACK_FILES_IN") //TE

- setDirectory:sender 
{
	id p = [SavePanel new];
	char *s;
	
	[[[p contentView] findViewWithTag:NX_OPTITLEFIELD]
		setStringValue:LS_MSG_UNPACK_FILES_IN];  //TE
	
	if ([p dirPanelRunModal] && (s = (char *)[p directory])){
		NXWriteDefault("Opener", "TmpDir", s);
		strcpy(TmpDir,s);
		[tmpDirTF setStringValue:s];
	}
	return self;
}

- (int)appOpenFile:(const char *)filename type:(const char *)aType 
{
	if (isCompSourceArchive((char *)filename))
		pending((char *)filename);
	else
		unpackFile((char *)filename);
	return 1;
}

- openRequest:sender 
{
	const char *directory, *const *files;
	static const char *const types[] = {
			"tar", "TAR", "tar.z", "tar.Z", "TAR.Z", "tar-z", "tar-Z", "TAR-Z",
			"taz", "TAZ", 
			"tar.gz", "tar.GZ", "TAR.GZ", "tar-gz", "tar-GZ", "TAR-GZ", 
			"tgz", "TGZ",
		"shar","SHAR","shar.z","shar.Z","SHAR.Z","shar-z","shar-Z","SHAR-Z",
			"shar.gz", "shar.GZ", "SHAR.GZ", "shar-gz", "shar-GZ", "SHAR-GZ",
			"PS", "EPS", "PS-Z", "ps-Z", "ps-z", "EPS-Z", "eps-Z", "eps-z",
			"PS-GZ", "ps-GZ", "ps-gz", "EPS-GZ", "eps-GZ", "eps-gz",
			"tar.bz2", "tar.BZ2","TAR.BZ2","bz2","BZ2",  //TE
			"uu", "UU",
			"lha", "Lha", "LHA",
			"lzh", "Lzh", "LZH",
			"arc", "Arc", "ARC",
			"arj", "Arj", "ARJ",
			"sit", "Sit", "SIT",
			"cpt", "Cpt", "CPT",
			"bin", "Bin", "BIN",
			"hqx", "Hqx", "HQX",
			"zip", "Zip", "ZIP",
			"zoo", "Zoo", "ZOO",
			"mime", "Mime", "MIME",
			"gz", "Gz", "GZ",
			"z", "Z",
			"compressed", "Compressed", "COMPRESSED",
			"gsm", "Gsm", "GSM",
			"au", "Au", "AU",
			NULL};
	id p = [[OpenPanel new] allowMultipleFiles:YES];
	char name[512];
	
	if ([p runModalForTypes:types]){
		files = [p filenames];		// list of multiple files
		directory = [p directory];
		while (files && *files) {
			strcpy(name, directory );
			strcat(name, "/" );
			strcat(name, *files );
			files++;
			unpackFile(name);
		}
	}
	return self;
}

- (BOOL)scratchFiles 
{
	char t[1024];
	FILE *p;
	char s[1024];
	BOOL b = NO;
	
	if (strcmp(TmpDir,"/tmp"))
		sprintf(t,"ls /tmp/O_* %s/O_*", TmpDir);
	else
		sprintf(t,"ls /tmp/O_*");
	p = popen(t,"r");
	if (p){
		if (fgets(s,sizeof s,p) && strncmp(s,"No match",8)) b = YES;
		pclose(p);
	}
	return b;
}

- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum 
    iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth 
    iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
	if (!iconPathList || strcmp(iconPathList, pathList)) {
		free(iconPathList);
		iconPathList = save(pathList);
	}
	return 0;
}

- processOutput:(char *)s 
{
	err("%s",s);
	return self;
}
	
- system:(char *)s 
{ // exec 's' and processOutput pops up a panel with output
	[Process new:s delegate:self];
	return self;
}

- setPackCommand 
{
	char t[1024], f[4096], F[4096], dir[1024], *p, files[8192];
	char c[8192], *curSuffix, *curCmd;
	int i;
	
	Again:
		curSuffix = (char *)[[packButton2 selectedCell] title];
		curCmd = (char *)[[packButton selectedCell] title];
		strcpy(files,Files);
		
		if (strcmp(curCmd,"create")==0){  // curCmd == "unpack"
			if (Reset) resetPackItems();
			Reset = 1;
			curSuffix = (char *)[[packButton2 selectedCell] title];
			i = findPSuffix(curSuffix);
			if (i == -1){
				[packPanel orderOut:self];
				err("Unknown archive format: %s",curSuffix);
				return 0;
			}
		
			expand_files(files,f,dir);
			expand_files(files,F,0);
			sscanf(f,"%s",t); stripnl(t);
			if (p = rindex(t,'/')) strcpy(t,p+1);
			strcat(t,curSuffix);
			p = tmp(t,"",0); strcpy(t,p);
		
			pexpand(i,c,dir,f,F,t);
			setText(packCmdText,c);
		} else {  // curCmd = "create" rather than "unpack"
			if (index(files,'\t')){
				char *p;
				p = index(files,'\t');
				*p = '\0';
				strcpy(MoreFiles,p+1);
				stripnl(files);
			} else {
				*MoreFiles = '\0';
			}
			expand_files(files,f,0);
			i = setSuffixes(files);
			if (i == -1){
				extern void NXBeep();
				[[packButton selectedCell] setTitle:"create"];
				NXBeep();
				resetPackItems();
				NXPing();
				goto Again;
			}
			
			expand(f,i,c);
			setText(packCmdText,c);
		}
	return self;
}

#define LS_TITLE_MAKE_NEW_ARCHIVE \
NXLocalizedString("Make new archive", NULL, "LS_TITLE_MAKE_NEW_ARCHIVE")  //TE

#define LS_TITLE_UNPACK_ARCHIVE \
NXLocalizedString("Unpack archive", NULL, "LS_TITLE_UNPACK_ARCHIVE")  //TE

- setPackOrUnpack:sender 
{
	char *s = (char *)[[sender selectedCell] title];

	[[packButton selectedCell] setTitle:s];
	if (strcmp(s,"create")==0)
		[packPanel setTitle:LS_TITLE_MAKE_NEW_ARCHIVE];  //TE
	else
		[packPanel setTitle:LS_TITLE_UNPACK_ARCHIVE];  //TE
	NXPing();
	resetPackItems();
	[self setPackCommand];
	return self;
}

- setUnpackSuffix:sender 
{
	char *s = (char *)[[sender selectedCell] title];

	[[packButton2 selectedCell] setTitle:s];
	NXPing();
	resetPackItems();
	[self setPackCommand];
	return self;
}

- setPackSuffix:sender 
{
// Change the default pack suffix in the Opener defaults to match the title
// on the selected cell.  Then use the title to build a pack command.
	char *s = (char *)[[sender selectedCell] title];

	NXWriteDefault("Opener", "ArchiveFormat", s);

	[[packButton2 selectedCell] setTitle:s];
	Reset = 0; 						// bandaid
	NXPing();
	[self setPackCommand];
	return self;
}

- packFiles:(char *)files 
{
	strcpy(Files,files);
	if (!*MoreFiles){
		if (index(Files,'\t') || findSuffix(Files) == -1){
			[packPanel setTitle:LS_TITLE_MAKE_NEW_ARCHIVE]; //TE
			[[packButton selectedCell] setTitle:"create"];
		} else {
			[packPanel setTitle:LS_TITLE_UNPACK_ARCHIVE]; //TE
			[[packButton selectedCell] setTitle:"unpack"];
		}
	}
	[packPanel makeKeyAndOrderFront:self];
	[[packButton2 selectedCell] setTitle:((char *)NXGetDefaultValue("Opener", 
															"ArchiveFormat"))];
	NXPing();
	resetPackItems();
	[self setPackCommand];
	return self;
}

- cancelPack:sender 
{
	[packPanel orderOut:sender];
	*MoreFiles = '\0';
	return self;
}

- pack:sender 
{
	char c[8192];

	getText(packCmdText,c);
	if (!blank(c)) [self system:c];
	[packPanel orderOut:sender];
 	if (*MoreFiles) [self packFiles:MoreFiles];
 	return self;
}

- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag {
	[self packFiles:iconPathList];
	*flag = YES;
	return 0;
}

- help:sender 
{
	if (!helpObject)
		 helpObject = [Help new];
	[helpObject generalHelp:sender];
	return self;
}

#define LS_MSG_CONFIRM_EDIT_TABLE \
NXLocalizedString("Edit the command table that controls Opener?\n(Unix experts only)", NULL, "LS_MSG_CONFIRM_EDIT_TABLE") //TE

- editTable:sender 
{
	char s[1024];
	
	strcpy(s,path("open"));

	if (confirm(LS_MSG_CONFIRM_EDIT_TABLE)) //TE
//		System("open -a Edit %s",path("Opener.table"));
		System("%s -a Edit %s", s, path("Opener.table")); //TE
	return self;
}

- suggestion:sender
{
	ComposeSuggestion();
	return self;
}

/*****************************************************************************/
/* Access methods start here                                                 */
/*****************************************************************************/

- (char *)version
{
	return "Version 3.4b Jun 1998"; //TE
}

@end
