
//	Copyright H. Giesen, University of Koblenz-Landau 1996


#import "Controller.h"
#import "BibliographicFile.h"
#import "BibTexView.h"
#import "DragView.h"
#import "MyBrowserCell.h"
#import "Preferences.h"
#import "TeXClass.h"
#import "FindClass.h"
#import "BrowserController.h"
#import "Shelf.h"
#import "BufferClass.h"
#import "MacroDB.h"
#import "HTML_Stuff.h"


#import	<ansi/time.h>
#import <stdarg.h>
#import <mach-o/arch.h>


#define	ON	YES
#define	OFF	NO
#define DRAGVIEWTAG	7

#define ui	unsigned int

#define sameFile(a,b) \
	( !NXOrderStrings( a, b, YES, -1, NULL ) )

char	*aFileName = NULL;

static	int	semaphore = 1;	// see windowDidBecomeKey:

const char *Bib_Version = "1.2a";



/***************		category for the PopUpList class	***********/

@implementation PopUpList(sizeTheButton)

- sizeButton:button
{
	NXRect	menuRect;
	
	[self sizeToFit];
	// the itemlist is the matrix of menucells
	[[self itemList] getCellFrame:&menuRect at:0 :0];
	[button sizeTo:menuRect.W :menuRect.H];

	return self;
}


@end



/*=========		category for the Textclass		===========*/

@implementation Text(printf)

- (void) addText:(char *)theText
{
	int length = [self textLength];
	[self setSel:length :length];
	[self replaceSel:theText];
	[self scrollSelToVisible];
}


- (void) clearText
{
	[self setSel:0 :[self textLength]];
	[self replaceSel:NULL];
}


- (void) clearAndWriteText:(char *)theText	// never used
{
	[self setSel:0 :[self textLength]];
	[self replaceSel:theText];
	[self scrollSelToVisible];
}

- (void) printf:(const char *)format, ...	// never used
{	
	va_list	ap;
	int		length = [self textLength];
	char	locBuffer[512];
	
	va_start( ap, format );
	vsprintf( locBuffer, format, ap );	
	va_end( ap );
	
	[self setSel:length :length];
	[self replaceSel:locBuffer];
	[self scrollSelToVisible];

}

@end

/*^^^^^^^^^^^^		category for the Textclass		^^^^^^^^^^^^*/




@implementation Controller


char	buffer[512];	//	global textbuffer
int		fileSeqNumber = 0;


- (void) prRect:(const char *)txt :(NXRect)r
{
	fprintf( stderr, "%s  x=%d y=%d w=%d h=%d\n",
		txt, (int)r.X, (int)r.Y, (int)r.W, (int)r.H );
}


- (void)alert:(char *)txt :(char *)para
{
	NXRunAlertPanel("Bibliography",
				txt,
				"  OK ",	// 1: default button
				NULL,		// 0: alternate
				NULL, 		//-1: other
				para
				);
}


- (void) clearButton:button
{
	[[button findViewWithTag:DRAGVIEWTAG] clear];
}


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


- (int)app:sender openFile:(const char *)filename type:(const char *)aType
{
	
	if( aFileName==NULL ){	// appDidInit not yet invoked
		aFileName = NXCopyStringBuffer( filename );
		return YES;
	}
	if( [self openFile:filename] ){
		[browserController resetBrowser:self];
		return YES;
	}
	return NO;
}


- initMenuCellsIn:(int)menuTag from:(int)first to:(int)last
{
	int		i;
	Menu	*subMenu;
	
	if( menuTag<10 )
		subMenu = [[[NXApp mainMenu] findCellWithTag:menuTag] target];
	else{
		subMenu = [[[NXApp mainMenu] findCellWithTag:menuTag/10] target];
		subMenu = [[subMenu findCellWithTag:menuTag] target];
	}
	if( subMenu )
		for( i=first; i<=last; i++ ){	// Document menu
			[[subMenu findCellWithTag:i]
				setUpdateAction:@selector(menuActive:) forMenu:subMenu];
		}
	//else fprintf( stderr, "menutag %d not found\n", menuTag );

	return self;
}


- initMenus
{
	[self initMenuCellsIn:DOCUMENT	from:OPEN		to:CLOSE];
	[self initMenuCellsIn:EDIT		from:CUT		to:SELECTALL];
	[self initMenuCellsIn:FIND		from:FINDPANEL	to:JUMPTOSELECTION];
	[self initMenuCellsIn:PREVIEW	from:PRVWINDOW	to:PRINT];

	[NXApp setAutoupdate:YES];
	return self;
}


static const char * dragTypes[3];

- appDidInit:sender
{
	openPanel = [OpenPanel new];
	[openPanel allowMultipleFiles:YES];
	
	listOfFiles = [[List alloc] initCount:10];

	if( BIBTEST==NO ){
		int row, col;
		MenuCell *cell = [[NXApp mainMenu] findCellWithTag:1111];
		Matrix	 *matrix = [[NXApp mainMenu] itemList];
		[matrix getRow:&row andCol:&col ofCell:cell];
		[matrix removeRowAt:row andFree:YES];
		[[NXApp mainMenu] sizeToFit];
		[[NXApp mainMenu] display];

		[[bufferedButton removeFromSuperview] free];
	}

	[bibWindow setMiniwindowIcon:"multiBook"];
	[bibWindow registerForDraggedTypes:&NXFilenamePboardType count:1];
	//[bibWindow registerForDraggedTypes:&NXAsciiPboardType count:1]; 

	[bibWindow setFrameUsingName:"MainWindow"];
	[bibWindow setFrameAutosaveName:"MainWindow"];

	[browserController setUp];
	[browserController setUpIcons:[fileDragBox findViewWithTag:DRAGVIEWTAG]
								 :[entryDragBox findViewWithTag:DRAGVIEWTAG]];
	[[NXApp appListener] setServicesDelegate:browserController];
	 
	litBrowser = [browserController theBrowser];
	[bibWindow makeKeyAndOrderFront:self];
	[bibWindow useOptimizedDrawing:YES];
	
	[betweenButton setIcon:"NXmenuArrow"];
	inspector = [BibTexView new];
	MyListPboardType = NXUniqueString( "My List Pboard Type" );
	dragTypes[0] = MyListPboardType;
	dragTypes[1] = NXAsciiPboardType;
	dragTypes[2] = NULL;
	[[fileDragBox findViewWithTag:DRAGVIEWTAG]
			registerForDraggedTypes:dragTypes count:2];

	preferences = [Preferences new];

	[deleteButton setEnabled:NO];
	[newEntryButton setEnabled:NO];

	// prepare the shelf
	[bibWindow disableDisplay];
	shelfController = [iconScrollView docView];
	[shelfController setBoxPrototype:fileDragBox];
	[shelfController loadShelfFromFile:[preferences shelfFile]];
	[[bibWindow reenableDisplay] displayIfNeeded];

	if( aFileName ){	// lazy evaluation
		if( [self openFile:aFileName] )
			[browserController resetBrowser:self];
	}
	else aFileName = "X";	// must not be NULL

	[self initMenus];
	preViewPushDown =
		[[self texObject] buildPreviewMenuFor:preViewButton];

	[preViewButton setEnabled:NO];
	[bibWindow makeFirstResponder:litBrowser];
	return self;
}



- (DragView *)viewFor:button
{
	return [button findViewWithTag:DRAGVIEWTAG];
}


- (BOOL) canCiteFromColumn:(int)col		// never used but useful
{
	int	theType = [browserController displayTypeInCol:col];
	
	return ( (theType==D_KEY_SORTED) || (theType==D_KEY_AS_READ) );
}


//	returns the new state of the menuCell
	// YES = enabled
	//	NO = disabled

- (BOOL)updateMenuFor:menuCell
{
	BOOL	isEnabled	= [menuCell isEnabled];
	BOOL	newState	= NO;	// .. and the compiler keeps silent
	int		cnt			= [[browserController selectedFiles] count];

	switch( [menuCell tag] ){
		case OPEN	: newState = YES; break;
		case NEW	: newState = YES; break;
		case SAVE	: newState = (cnt>0); break;
		case SAVEAS : newState = (cnt>0); break;
		case SAVETO : newState = NO; break;	// not yet implemented
		case SAVEALL: newState = NO; break;
		case REVERT	: newState = (cnt>0); break;
		case CLOSE	: newState = (cnt>0); break;

		case CUT	: newState = NO; break;
		case COPY 	: newState = NO; break;
		case COPYCITE 	:
				newState = [[[self viewFor:entryDragBox] getList] count]>0;
				break;
				
		case PASTE	: newState = NO; break;
		case SELECTALL : newState = [listOfFiles count]?YES:NO; break;
		case FINDNEXT  : newState = (cnt>0); break;
		case ENTERSELECTION : newState = NO; break;

		case PRVWINDOW	:	newState = YES;
							break;
		case RUNLATEX	:
		case RUNRTF		:
		case RUNHTML	:
				newState = ([[[self viewFor:entryDragBox] getList] count]>0)
						&& ([texObject isRunning]==NO);
						  break;
		case PRINT		: newState = texObject!=nil;
							break;
		default : newState = isEnabled;	// do not change
		}

	return newState;
}


- (BOOL)menuActive:menuCell
{
	id		delegate = [[NXApp keyWindow] delegate];
	BOOL	isEnabled = [menuCell isEnabled];
	BOOL	newState=NO;	// .. and the compiler keeps silent

	if( [delegate respondsTo:@selector(updateMenuFor:)] ){
		newState = [delegate updateMenuFor:menuCell];	
		if( isEnabled!=newState ){
			[menuCell setEnabled:newState];
			return YES;
		}
		return NO;
	}
	return NO;
}


- info:sender
{
	const	NXArchInfo  *architecture;
	char	hostNameStr[MAXHOSTNAMELEN];
	const	NXScreen *screen;
	const	char *dTxt;
	NXRect	screenBounds;
	NXWindowDepth	depth;
	char	locBuf[64];
	
	if( !infoPanel ){
		[NXApp loadNibSection:"info.nib" owner:self];

		[infoPanel setFrameUsingName:"InfoPanel"];
		[infoPanel setFrameAutosaveName:"InfoPanel"];

		sprintf( locBuf, "Version %s", Bib_Version );
		[versionText setStringValue: locBuf];

		gethostname(hostNameStr, MAXHOSTNAMELEN);
		[hostNameText setStringValue: hostNameStr];
		//[hostNameText setStringValue: [NXApp hostName]];

		architecture = NXGetLocalArchInfo();
		[procTypeText setStringValue: architecture->description];
		
		screen = [[NXApp mainMenu] screen];
		screenBounds = screen->screenBounds;
		depth = screen->depth;
		
		switch( depth ){ 
			case NX_DefaultDepth			: dTxt = "default"; break;
			case NX_TwoBitGrayDepth			: dTxt = "2 bit gray"; break;
			case NX_EightBitGrayDepth		: dTxt = "8 bit gray"; break;			
			case NX_TwelveBitRGBDepth		: dTxt = "12 bit RGB"; break;
			case NX_TwentyFourBitRGBDepth	: dTxt = "24 bit RGB"; break;
			case 514						: dTxt = "8 bit RGB"; break;
			default : dTxt = "unknown screentype"; break;
		}
		[screenTypeText setStringValue: dTxt];
		sprintf( locBuf, "%d x %d",
			(int)screenBounds.W, (int)screenBounds.H );
		[screenSizeText setStringValue: locBuf];
	}
	[infoPanel orderFront:self];
	return self;
}


- (void) insertFile:(BibliographicFile	*)theFile
{
	int	j = [listOfFiles count] - 1;

	while( j>=0 ){
		if( strcasecmp( [[listOfFiles objectAt:j] fileName],
				[theFile fileName] ) > 0 ) j--;
		else break;
	}
	[listOfFiles insertObject:theFile at:j+1];
}


- newFile:sender
{	
	BibliographicFile	*fileObj;

	sprintf( buffer, "%s/UNTITLED-%d.bib",
			NXHomeDirectory(), (++fileSeqNumber) );
	fileObj = [[BibliographicFile alloc] initNewFile:buffer];
	if( fileObj ){
		[self insertFile:(BibliographicFile	*)fileObj];
		[fileObj setDirty];
	}
	else{
		[self alert:"cannot create file\n\n    %s\n": buffer ];
	}

	[browserController resetBrowser:self];
	[browserController selectFile:fileObj];	
	[bibWindow makeKeyAndOrderFront:self];
	return self;
}


- (BOOL) openFile:(const char *)fullName
{
	int	i;
	BibliographicFile	*fileObj;


	if( ![BibliographicFile canLoadFromFile:fullName] ){
		[self alert:
			"file >%s<\n        is not a bib-file" :(char *)fullName ];
		return NO;
	}
	
	// check if file is already loaded
	for( i=0; i<[listOfFiles count]; i++ ){
		if( sameFile( fullName, [[listOfFiles objectAt:i] fullPath] ) ){
			[self alert:
				"file >%s<\n        is already loaded" :(char *)fullName ];
			return NO;
		}
	}

	fileObj = [[BibliographicFile alloc] initFromFile:fullName];
	if( fileObj ){
		[self insertFile:(BibliographicFile	*)fileObj];
		// update icon on the shelf
		[shelfController attachFile:fullName toObject:fileObj];
		return YES;
	}

	[self alert:
			"cannot open >%s<" :(char *)fullName ];
	return NO;
}


- (BOOL) openFile:(const char *)dir :(const char *)fName
{
	sprintf( buffer, "%s/%s", dir, fName );
	return [self openFile:buffer];
}


- open:sender	// multiple files allowed (type is 'bib')
{
	int	rtn;
	const	char* const*  fileName = {NULL};
	const	char* dir;  // directory


	dir = [openPanel directory];
	if ( (rtn=[openPanel runModalForTypes:[BibliographicFile fileTypes]] )
		&& (fileName = [openPanel filenames]) ) {
		int i=0;

		dir = [openPanel directory];
		while( fileName[i] != NULL ){
			[self openFile:dir :fileName[i]];
			i++;
		}
		[browserController resetBrowser:self];
	}
	else{
		if( rtn==NX_OKTAG )
			[self alert:"cannot not open\n\n      %s\n" :(char *)fileName ];
	}	
	[bibWindow makeKeyAndOrderFront:self];

	return self;
}


// saveFile returns -1 on error, otherwise:
//	NX_CANCELTAG or NX_OKTAG
- (int)saveFile:(BibliographicFile *) theFile
{
	char	buffer[1024];
	char	*fName;
	BOOL	isNew = NO;

	filenameChanged = NO;
	if( (isNew=[theFile isNewFile]) ){
		int	rtn;
		SavePanel *savePanel = [SavePanel new];

		fName = NXCopyStringBuffer( [theFile fullPath] );	// save old name
		[savePanel setRequiredFileType:"bib"];
		rtn = [savePanel
					runModalForDirectory:[theFile fullPath]
					file:[theFile fileName]];
		// Returns NX_OKTAG (if the user clicks the OK button)
		// or NX_CANCELTAG (if the user clicks the Cancel button)
	
		if( rtn == NX_CANCELTAG ) return NX_CANCELTAG;
		filenameChanged = YES;
		[theFile changeFilenameTo:[savePanel filename]];
		rtn = [theFile save];
		if( rtn<0 ){;
			[theFile changeFilenameTo:fName];	// rename
		}
		else [browserController resetBrowser:self];	// show the new file
		free( fName );
		return rtn;
	}
	else{	// save backup
			// did someone else change the file ??
		struct	stat fBuf;
		int	last = [theFile lastModified];
		int	rtn = stat([theFile fullPath], &fBuf);

		if( rtn<0 ) perror("stat error");
		if( fBuf.st_mtime>last ){
			[self alert:"%s has been edited by someone else."
			"Do you want to overwrite?" :[theFile fullPath]];
		}
		// instead of "system mv . . . " use:
		// performFileOperation:source:destination:files:options:
/*****
		[[Application workspace]
			performFileOperation:WSM_MOVE_OPERATION
			source:"/tmp" 
			destination:NULL 
			files:(const char *)(fff + 1) 
			options:NULL];
****/
		sprintf( buffer, " mv %s %s~",
			[theFile fullPath], [theFile fullPath] );
		system( buffer );
	}
	return [theFile save];
}


- close:sender
{
	List	*closeList = [browserController selectedFiles];
	BibliographicFile	*fileObj;
	int	i;
					
	if( [closeList count]==0 ){	// should never occur
		fprintf( stderr, "systemerror in close\n" );
		return self;
	}

	for( i=0; i<[closeList count]; i++ ){
		fileObj = [[closeList objectAt:i] fileObject];
		if( [fileObj isDirty]==YES ){
			int	rtn = 
				NXRunAlertPanel("Bibliography", 	// title
					"file >%s<\n     did change",	// message
					"  save and close ",	// 1: default button
					"  close anyway  ",		// 0: alternate
					"  cancel  ", 			//-1: other
					[fileObj fullPath]
					);
				if( rtn==NX_ALERTDEFAULT ){// save and close
					[self saveFile:fileObj];
				}
				if( rtn==NX_ALERTOTHER ) break;	// cancel
		}
		if( fileObj==lastMainFile ){
			lastMainObject = nil;
			lastMainFile = nil;
		}
		[findObject deleteFile:fileObj];	// delete one by one
		[shelfController detachObject:fileObj];

		[listOfFiles removeObject:fileObj];
		[fileObj free];
	}
	[[MacroDB new] fileDidChangeTo:nil];
	[browserController resetBrowser:self];

	return self;
}


- revertToSaved:sender
{
	BibliographicFile	*fileObj;
	List	*closeList = [browserController selectedFiles];
	int	i, rtn;
					
	if( [closeList count]==0 ){	// should never occur
		fprintf( stderr, "SystemError in revertToSaved\n" );
		return self;
	}

	for( i=0; i<[closeList count]; i++ ){
		fileObj = [[closeList objectAt:i] fileObject];

		//if( [fileObj isDirty]==NO ) continue;	// ?????
		if( [fileObj isNewFile]==YES ) continue;
		if( fileObj==lastMainFile ){
			lastMainObject = nil;
			lastMainFile = nil;
		}
		rtn = NXRunAlertPanel("Bibliography", 	// title
				"revert %s ?",	// message
				"  OK  ",				// 1: default button
				"  NO  ",		// 0: alternate
				NULL, 			//-1: other
				[fileObj fullPath]
			  );
		if( rtn==NX_ALERTALTERNATE ) continue;	// NO

		[findObject deleteFile:fileObj];	// delete one by one
		[listOfFiles removeObject:fileObj];
		[shelfController detachObject:fileObj];
		[self openFile:[fileObj fullPath]];	// insert in listOfFiles	

		[fileObj free];
	}
	
	[browserController resetBrowser:self];

	return self;
}


- (void) fileNameOf:(BibliographicFile	*)aFile didChangeTo:(const char *)aName
{

	[aFile changeFilenameTo:aName];	// sets 'isDirty' to YES

	// remove fileobj and insert it at the right (sorted) place
	[listOfFiles removeObject:aFile];
	[self insertFile:aFile];
	[browserController resetBrowser:self];

	// file could be in the find window
	[findObject showFindList];

	[shelfController detachObject:aFile];
}


- (void) shelfText:(const char *)txt
{
	[fileOnShelfText setStringValue:txt];
}


- save:sender
{
	int	rtn, i;
	SavePanel	*savePanel = [SavePanel new];
	List		*saveList = [browserController selectedFiles];
	BibliographicFile	*fileObj;
	BOOL		isNew;

	if( [saveList count]==0 ) return self;

	[savePanel setAccessoryView:saveAccessoryView];
	for( i=0; i<[saveList count]; i++ ){
		fileObj = [[saveList objectAt:i] fileObject];
		//fprintf( stderr, "save %s\n", [fileObj fullPath] );
		isNew = [fileObj isNewFile];
		if( (rtn = [self saveFile:fileObj])<0 ){
			[self alert:"cannot not save\n\n      %s\n" :[fileObj fullPath] ];
			continue;
		}
		if( rtn==NX_CANCELTAG ) continue; 
	
		// rtn is NX_OK
		// did the name change ?
		if( filenameChanged==NO ) continue;
		if( (isNew==NO)
			&& sameFile( [savePanel filename], [fileObj fullPath] ) ){
			continue;
		}
		// filename did change
		//[shelfController detachObject:fileObj];
		[self fileNameOf:fileObj didChangeTo:[savePanel filename]];
	}
	return self;
}


- saveAs:sender
{
	int	rtn, i;
	char	*fName;
	SavePanel	*savePanel = [SavePanel new];
	BibliographicFile	*fileObj = [browserController selectedFile];

	[savePanel setRequiredFileType:"bib"];
	[savePanel setAccessoryView:saveAccessoryView];
	rtn = [savePanel
				runModalForDirectory:[fileObj fullPath]
				file:[fileObj fileName]];
	// Returns NX_OKTAG (if the user clicks the OK button)
	// or NX_CANCELTAG (if the user clicks the Cancel button)

	if( rtn == NX_CANCELTAG ) return self;

	// exists another file with this name?
	for( i=0; i<[listOfFiles count]; i++ ){
		if( sameFile( [savePanel filename],
					  [[listOfFiles objectAt:i] fullPath] ) ){
			[self alert:
				"filename >%s<\n        already exists\ncannot SaveAs..."
				:(char *)[savePanel filename] ];
			return self;
		}
	}
	// the new filename is ok
	fName = NXCopyStringBuffer( [fileObj fullPath] );	// save old name
	[fileObj changeFilenameTo:[savePanel filename]];
	rtn = [fileObj save];
	if( rtn<0 ){
		[self alert:"cannot not save\n\n      %s\n" :[fileObj fullPath] ];
		[fileObj changeFilenameTo:fName];
	}
	else [self fileNameOf:fileObj didChangeTo:[savePanel filename]];

	free( fName );
	return self;
}



- appWillTerminate:sender
{
	int		i;
	BOOL	review = YES;

	if( [texObject isRunning] ) [texObject stopRunning:self];

	// are there some changed files ?
	for( i=0; i<[listOfFiles count]; i++ ){	
		BibliographicFile	*fileObj = [listOfFiles objectAt:i];
		
		if( [fileObj isDirty]==YES ){
			int	rtn = 
			NXRunAlertPanel("Bibliography", // title
				"There are edited windows",	// message
				"  Review Unsaved ",		// 1: default button
				"  Quit anyway  ",			// 0: alternate
				"  Cancel " 				//-1: other
			);
			if( rtn==NX_ALERTDEFAULT ){	// Review Unsaved
				review = YES;
				break;
			}
			if( rtn==NX_ALERTALTERNATE ){	// Quit anyway
				review = NO;
				break;
			}
			if( rtn==NX_ALERTOTHER ) return nil;	// cancel
		}
	}

	if( review==YES )
		for( i=0; i<[listOfFiles count]; i++ ){	
			BibliographicFile *fileObj = [listOfFiles objectAt:i];
			
			if( [fileObj isDirty]==YES ){
				int	rtn = 
				NXRunAlertPanel("Bibliography", 	// title
					"changed file:\n%s",	// message
					"  save ",				// 1: default button
					"  close anyway  ",		// 0: alternate
					"  cancel ", 			//-1: other
					[fileObj fullPath]
				);
				if( rtn==NX_ALERTDEFAULT ){	// save
					if( [self saveFile:fileObj]<0 ){
						[self alert:
						"cannot not save\n\n      %s\n" :[fileObj fullPath] ];
						return nil;
					}
				}
				if( rtn==NX_ALERTOTHER ) return nil;	// cancel
			}
		}
	
	//	remove the temporary directory
	if( [preferences shallRemoveTempFiles] ){
		char *fff = strrchr( [preferences tempDirectory], '/' );
	
		if( fff==NULL ) return self;
		[[Application workspace]
			performFileOperation:WSM_DESTROY_OPERATION
			source:"/tmp" 
			destination:NULL 
			files:(const char *)(fff + 1) 
			options:NULL];
	}
	return self;
}


- app:sender fileOperationCompleted:(int)tag
{
	//fprintf( stderr, "fileOperationCompleted %d\n", tag );
	return self;
}


- app:sender powerOffIn:(int)ms andSave:(int)aFlag
{
	return [self appWillTerminate:self];
}


- preferences:sender
{
	[preferences show];
	return self;
}


- setFiledataFor:(BibliographicFile	*)fileObject
{
	int	cpuTime;
	
	if( fileObject==NULL ){
		[fileNameText setStringValue:""];
		[fileSizeText setStringValue:""];
		[fileTimeText setStringValue:""];
		[numberOfEntriesText setStringValue:""];
		return self;
	}

	[fileNameText setStringValue:[fileObject fullPath]];
	sprintf( buffer, "filesize: %d byte", [fileObject fileSize] );
	[fileSizeText setStringValue:buffer];
	if( [fileObject entries]==1 )
		sprintf( buffer, "1 entry" );
	else
		sprintf( buffer, "%d entries", [fileObject entries] );
	[numberOfEntriesText setStringValue:buffer];
	cpuTime = [fileObject uTime] + [fileObject sTime];
	sprintf( buffer, "%d.%03d sec", cpuTime/1000, cpuTime%1000 );
	[fileTimeText setStringValue:buffer];
	return self;
}


//	returns a list of all loaded files
- (List *) listOfFiles
{
	return listOfFiles;
}


- selectedFile
{
	return [browserController selectedFile];
}


- (void)colZeroIsFilled
{
	[inspector setEntry:(lastMainObject = nil)
				 inFile:(lastMainFile = nil)];

	[self setFiledataFor:NULL];
	[newEntryButton setEnabled:NO];
	[findButton setEnabled:YES];
	[preViewButton setEnabled:NO];
}


- browserDidChange:sender	// sender is BrowserController
{
	DragView	*fileView = [self viewFor:fileDragBox];
	DragView	*entryView = [self viewFor:entryDragBox];
	BibliographicFile	*fileObj;

	int cnt;

	cnt = [[entryView getList] count];

	if( cnt>0 ){	// there are selected entries
		[deleteButton setEnabled:YES];
		[preViewButton setEnabled:YES];
		if( cnt==1 )
			 lastMainObject = [[[entryView getList] objectAt:0] object];
		else lastMainObject = nil;
	}
	else {
		[deleteButton setEnabled:NO];
		[preViewButton setEnabled:NO];
		lastMainObject = nil;
	}

	//	is exactly one file selected ?
	if( [fileView count]==1 ){	// exactly one file selected
		[newEntryButton setEnabled:YES];
		fileObj = [[[fileView getList] objectAt:0] fileObject];
		if( lastMainObject==nil )
			[[MacroDB new] fileDidChangeTo:fileObj];
	}
	else{
		[[MacroDB new] fileDidChangeTo:nil];
		[newEntryButton setEnabled:NO];
		fileObj = nil;
	}

	//	are some entries selected?
	[self setFiledataFor:fileObj];
	lastMainFile = fileObj;

	[inspector setEntry:lastMainObject inFile:lastMainFile];
	return self;
}


//	let this the browserController do !!!!
- deleteEntry:sender	// (only) called from delete-button
{
	Matrix	*matrix = [litBrowser matrixInColumn:[litBrowser lastColumn]];
	List	*selList = [matrix getSelectedCells:nil];
	BibliographicFile	*theFile = nil;	// makes the compiler happy
	int		i, row, col;

	if( [selList count]==0 ){
		[selList free];
		return self;
	}
	
	[findObject deleteEntries:selList];	// delete all in findpanel
	theFile = [[selList objectAt:0] fileObject];
	[theFile setDirty];

	//for( i=0; i<[selList count]; i++ ){
	for( i=[selList count]-1; i>=0; i-- ){
		MyBrowserCell *cell = [selList objectAt:i];

		if( lastMainObject==[cell object] ) lastMainObject=nil;
		[theFile deleteBibObject:[cell object]];	// delete in file
		[matrix getRow:&row andCol:&col ofCell:cell];
		[matrix removeRowAt:row andFree:NO];

	}
	[deleteButton setEnabled:NO];
	[matrix sizeToCells];
	[matrix sendAction];
	[matrix display];
	[selList free];
	return self;
}


- newEntry:sender	// of predefined type !!!!
{
	[browserController deselectColumn:-1];
	[deleteButton setEnabled:NO];
	lastMainObject = nil;	
	[inspector newEntryFor:lastMainFile];
	return self;
}



//	semaphore - operations

- (void) P:sender
{
	semaphore--;
}


- (void) V:sender
{
	semaphore++;
}


- (int)semaphore:sender;
{
	return semaphore;
}


- theShelf
{
	return shelfController;
}

- (void) getFrameOfFileIcon:(NXRect *)frame
{
	[fileDragBox getFrame:frame];
}


- (BrowserController *)browserController
{
	return browserController;
}


- (DragView *)fileDragView
{
	return [fileDragBox findViewWithTag:DRAGVIEWTAG];
}


- (TeXClass *)texObject
{
	if( !texObject ) texObject = [[TeXClass alloc] init];
	return texObject;
}


- macro:sender
{
	id macro;
	macro = [[MacroDB new] show:sender];
	return self;
}



/************		dragging		************/

// includesType() returns YES if type is included within types.

static BOOL includesType (const NXAtom *types, NXAtom type)
{
	if (types) while (*types){
		//fprintf( stderr, "%s\n", *types );
		if (*types++ == type) return YES;
	}
	return NO;
}


- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
	return [self draggingUpdated:sender];
}

- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
	Pasteboard *pboard;
	char *data, *lastDot, *file, *tab;
	int length;

	if( [sender isDraggingSourceLocal]==YES ){
		//fprintf( stderr, "it's local\n" );
		return NX_DragOperationNone;
	}
	
	if( [sender draggingSourceOperationMask] & NX_DragOperationGeneric) {
		pboard = [sender draggingPasteboard];

		if( includesType([pboard types], NXFilenamePboardType) ){
			[pboard readType:NXFilenamePboardType data:&data length:&length];
			// is it a list of files ?
			file = data;
			while (file) {
				if (tab = strchr(file, '\t')) {
					*tab = '\0';
				}
				lastDot = rindex( file, '.' );
				if( lastDot==0 ) return NX_DragOperationNone;
				if (
					(strcmp( lastDot, ".bib" ) && strcmp( lastDot, ".bib~" ))
				   ) {	// use types !!!
					return NX_DragOperationNone;
				}
				file = (tab) ? ++tab : NULL;
			}
			return NX_DragOperationGeneric;
		}

		if( includesType( [pboard types], NXAsciiPboardType) ){		
			return NX_DragOperationGeneric;
		}

	}
	return NX_DragOperationNone;
}

- draggingExited:sender
{
	//fprintf( stderr, "draggingExited\n" );
	return self;
}


- (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
{
	char *data, *file, *tab;
	int length;

	Pasteboard *pboard = [sender draggingPasteboard];

	if( includesType([pboard types], NXFilenamePboardType) ){
		[pboard readType:NXFilenamePboardType data:&data length:&length];

		file = data;
		while (file) {
			if (tab = strchr(file, '\t')) {
				*tab = '\0';
			}
			[self openFile:file];
			file = (tab) ? ++tab : NULL;
		}
	
		[browserController resetBrowser:self];
		return YES;
	}

	return NO;
}


- windowDidMove:sender
{
	return self;
}


- update:obj for:aFile isNew:(BOOL)q shiftLit:(BOOL)sl
{
	List	*aList = [browserController selectedFiles];

	if( [aList count]!=1 ){	// more than one file selected
		return self;
	}

	if( [[aList objectAt:0] fileObject]!=aFile ){	// is this file selected ?
		return self;
	}

	[bibWindow disableDisplay];
	[browserController entryObjectChanged:obj];

	[deleteButton setEnabled:YES];

	[preViewButton setEnabled:YES];
	[self setFiledataFor:aFile];
	lastMainObject = obj;
	lastMainFile = aFile;

	[[bibWindow reenableDisplay] displayIfNeeded];
	return self;
}


- objectForFileNamed:(const char *)fName
{
	int	i;
	for(i=0; i<[listOfFiles count]; i++ ){
		if( sameFile( fName, [[listOfFiles objectAt:i] fullPath] ) ){
			return [listOfFiles objectAt:i];
		}
	}
	return nil;
}


- setBacking:sender
{
	if(0)fprintf( stderr, "state is %d   ", [sender state] );
	[bibWindow setBackingType:
		([sender state]==1) ? NX_BUFFERED :NX_RETAINED];
	if(0)fprintf( stderr, "  backingType is %d\n",
			[bibWindow backingType] );
	return self;
}


- windowDidBecomeKey:sender
{
	if( [self semaphore:self] <= 0 ) return self;

	[self P:self];
	if( sender==bibWindow ){
		if( lastMainObject==nil )
			[inspector setEntry:lastMainObject inFile:lastMainFile];
		else{
			[inspector setEntry:lastMainObject inFile:lastMainFile ];
		}
		[bibWindow makeFirstResponder:litBrowser]; 	
		[findObject setFindText];
	}

	[self V:self];
	return self;
}


- windowDidResize:sender
{
	[browserController windowDidResize:sender];
	[shelfController windowDidResize:sender];
	return self;
}


- didFileSystemChange	// never used
{
	fprintf( stderr, "fSys changed %d\n",
		[[Application workspace]didFileSystemChange]);
	return self;
}



- LaTeXFromList:(List *)aList tag:(int)tag
{
	if( [aList count]<=0 ) return self;

	if( !texObject ) texObject = [[TeXClass alloc] init];
	[texObject runTeXfor:aList tag:tag];
	return self;
}


- prepareLaTeX:(int)tag
{
	if( [texObject isRunning] ){
		[self alert:"bibtex is still running, please wait\n": NULL ];
		return self;
	}
	[self LaTeXFromList:[[self viewFor:entryDragBox] getList] tag:tag];
	return self;
}


- LaTeX:sender	// from menu LaTeX -> RUNLATEX/RUNRTF/RUNHTML
{
	id	delegate = [[NXApp mainWindow] delegate];
	if( [delegate respondsTo:@selector(prepareLaTeX:)] )
			[delegate prepareLaTeX:[[sender selectedCell] tag]];
	return self;
}


- HTML:sender
{
	[[HTML_Stuff new] writeHTMLfor:[listOfFiles objectAt:0]];
	return self;
}


- showPanel:sender
{	
	if( !texObject ) texObject = [[TeXClass alloc] init];
	[texObject showPanel];
	return self;
}


- printDvi:sender
{
	[texObject printDvi:sender];
	return self;
}


// copy selected entries to copyPasteBoard

- copyCitationForEdit
{
	[[self viewFor:entryDragBox] copyCitation:self];
	return self;

}


- copyCitation:sender	//€sender is a menu-button
{
	id main = [NXApp mainWindow];
	
	if( [[main delegate] respondsTo:@selector(copyCitationForEdit)] )
		[[main delegate] copyCitationForEdit];
	return self;
}


- (int) entryDragMethod
{
	// 0 : drag as citation
	// 1 : drag as citation
	return [entryDragButton selectedRow];
}


// copy selected text to findPasteBoard

- enterSelectionFor:responder
{
	NXSelPt start, end;
	Pasteboard *pboard;
	int	length;

static	BufferClass *viewBuffer = nil;	// local buffer 

	[responder getSel:&start :&end];
	if( (length=end.cp-start.cp)==0 ) return self;

	if( viewBuffer==nil )
		viewBuffer = [[BufferClass alloc] initBufferSize:length+1];
	else [viewBuffer setMinSizeTo:length+1];
	[responder getSubstring:[viewBuffer buffer]
		start:start.cp length:length];	// copy selection

	[viewBuffer setLength:length];

	// write to NXFindPboard
	pboard = [Pasteboard newName:NXFindPboard];
	[pboard declareTypes:&NXAsciiPboardType num:1 owner:nil];
	[pboard writeType:NXAsciiPboardType
		data:[viewBuffer buffer] length:length];

	[findObject setFindText];
	return self;
}



/*	*********	suggestions   ********	*/

// The following code was send to me by Michael Hawley (c)
// Carefully modified by Sven Lehmann to fit at Zoom.app

/*
 * Implement the "suggestion" box, to catch user's comments,
 * and also a simple tracking mechanism, to send a blip of
 * mail from the first-time user.  This is akin to releasing
 * helium balloons with postcards that say "If you find me,
 * send this card back to . . ." -- it should be interesting
 * to the community to track the free flow of software on
 * the ethernet.
 *
 * If you have any concerns about this, please check with me.
 *
 * Michael Hawley
 * mike@media-lab.mit.edu
 */

static char *stripnl(char *s)
{
	char	*p;

	for (p=s;*p;p++) if (*p == '\n' || *p == '\r') *p = '\0';
	return s;
}

char *execstr(s) char *s;
{
	FILE	*f = popen(s,"r");
	char	*p = s;

	*s = '\0';
	if (f) {
		while (fgets(p,256,f)) stripnl(p), p += strlen(p);
		pclose(f);
	}
	return s;
}


- suggestion: sender
{
	#define call(a,b) [s performRemoteMethod: a with: b length: strlen(b) + 1]

	char	subj[256], w[256] = "whoami";
	char	body[4096]= "\n\n"
						" I have these electronic comments:\n\n\n"
						"   <insert comments & suggestions here>\n\n\n"
						"             best regards,\n"
						"                             ";

	id	s = [NXApp appSpeaker];

	NXPortFromName("Mail", NULL);	// make sure app is launched
	[s setSendPort: NXPortFromName("MailSendDemo", NULL)];

	sprintf(subj,
		"Comments and suggestions for BibTeXfrontend (%s)", Bib_Version);
	strcat(body, execstr(w));
	strcat(body, "\n");
	call("setTo:", "giesen@informatik.uni-koblenz.de");
	call("setSubject:", subj);
	call("setBody:", body);

	return self;
}


- showFile:(const char *)aFile
{
	char buf[MAXPATHLEN];
	NXBundle *m = [NXBundle mainBundle];

	// load the file from the bundle
	if( [m getPath:buf forResource:aFile ofType:"rtf"] ){
		[[Application workspace] openFile:buf withApplication:"Edit"];
	}
	return self;
}


- showReadme:sender
{
	return [self showFile:"Readme"];
}


- showUsage:sender
{
	return [self showFile:"Usage"];
}


- app:sender willShowHelpPanel:panel
{
#ifdef PLP

	char path[MAXPATHLEN + 1];

	fprintf( stderr,"%s willShowHelpPanel at %s\n",
		[sender name], [panel helpDirectory] );

    sprintf (path, "%s/%s", [panel helpDirectory],
      "Edit/Cut.cmd.rtfd");
    [panel showFile:"/tmp/Edit/Cut.cmd.rtfd" atMarker:NULL];
#endif

	[panel setMiniwindowIcon:"Images/help"];
	return self;
}

- window2eps:sender
{
	//NXStream	*imageStream;
	//NXRect		frame;

	id theWindow = [NXApp keyWindow];
	
	if( theWindow==nil ){
		fprintf( stderr, "there is no keyWindow\n" );
		theWindow = [NXApp mainWindow];
		if( theWindow==nil ){
			fprintf( stderr, "there is no mainWindow\n" );
			return self;
		}
	}

	[theWindow printPSCode:self];	// method to create a file
	return self;
}


@end
