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


#import "Preferences.h"
#import "Controller.h"
#import "BibliographicFile.h"
#import "BibTexView.h"
#import "Shelf.h"
#import "MacroDB.h"
#import	"TabMatrix.h"

#import	<dbkit/dbkit.h>
#import	<ansi/time.h>
#import <sys/types.h>
#import <pwd.h>

typedef struct _passwd { /* see getpwent(3) */
	char *pw_name;
	char *pw_passwd;
	int  pw_uid;
	int  pw_gid;
	int  pw_quota;
	char *pw_comment;
	char *pw_gecos;
	char *pw_dir;
	char *pw_shell;
} passwd;

#define ADD	4
#define REN	2
#define DEL	1

extern int errno;

static unsigned char buttonTable[] = {
	0, DEL, ADD, ADD,
	0, DEL, ADD+REN, ADD+REN
};


/****************
	Es werden zwei Listen verwaltet:
	1: die Liste aller Entrynames -> entryNameList
	2: die Eigenschaften eines jeden Entry findet man
	   in der configList bei mode[i]
	
	zu einem gegebenen Index i findet man in entryNameList[i] den Namen
	des Entry und in configList[i] alle zugehoerigen Eigenschaften
	
	Wichtig:
		Sonderbehandlung von STRING und PREAMBLE und COMMENT
	Diese "Entries" stehen am Ende der entryNameList. Eine Beschreibung
	der Eigenschaften in der configList fehlt dagegen, weil immer
	eine Sonderbehandlung erforderlich ist. Die Liste der Entrynames
	ist also groesser als die Liste der Eigenschaften. Die entryNameList
	soll beim Erstellen von PopUpMenus verwendet werden.
****************/

static id thePreference = nil;

static	NXAtom	STRING ;
static	NXAtom	PREAMBLE;
static	NXAtom	COMMENT;

@implementation Preferences


+ new
{
	if( thePreference==nil ){
		thePreference = [[super alloc] init];
	}

	return thePreference;
}


+ alloc
{
    return [self notImplemented:_cmd];
}

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


- (int)findIndexFor:(int)inx :(unsigned char)label
{
	int	i = 0;
	while( toggleList[i].entryIndex ){
		if( (toggleList[i].entryIndex==inx)
		 && (toggleList[i].label==label) ){	// that's it
			return i;
		}
		i++;
	}
	//	not found, create a new entry
	toggleList[i].entryIndex = inx;
	toggleList[i].label = label;
	toggleList[i].state = 0;
	return i;
}

//	NO CHECK ?????
- (void)insert:(NXAtom)txt at:(int)inx
{
	if( toggleList[inx].txt0 ){	// second entry
		toggleList[inx].txt1 = txt;
		if( inx ){
		  if( toggleList[inx].entryIndex == toggleList[inx-1].entryIndex )
			toggleList[inx].tglButtonInx = toggleList[inx-1].tglButtonInx + 1;
		  else toggleList[inx].tglButtonInx = 0;
		}
	}
	else toggleList[inx].txt0 = txt;
}


- (toggleListType *)toggleList
{
	return toggleList;
}


- (void)activatePopUpList
{
	// connect the popUpList to the trigger Button
	// this can only be done if the nib-file is loaded
	// before loading we have no popUpButton
	[popUpButton setFont:[Font newFont:"Courier" size:12.0 ]];
	NXAttachPopUpList( popUpButton, popUpList );
	[popUpList sizeButton:popUpButton];
	[[popUpList setAction:@selector(bibPopup:)] setTarget:self];
	entryIndex = 0;
}


//	show all options for entryType at index 'inx' in entryNameList

- (void)showOptionsFor:(int)inx
{
	if( [popUpList target]!=self ) [self activatePopUpList];
	[[showBox window] disableDisplay];
	[[showBox window] endEditingFor:nil];

	// 	fill tableView (assuming 'inx' did change)
	[tableView rowsChangedFrom:0 to:[configList count]-1];
	[tableView layoutChanged:self];
	[[[showBox window] reenableDisplay] display];
}


- (void)setButtonsFor:(char *)name
{
	int	mode, state;
	int	inx;
	
	mode = ([tableView selectedRow]>=0) ? ADD : 0;
	if( (inx=[self indexOfFieldname:name])<0 ) mode |= REN;
	if( inx>=0 ) mode |= DEL;
	state = buttonTable[ mode ];
	[addButton setEnabled:(state&ADD)==ADD];
	[renameButton setEnabled:(state&REN)==REN];
	[deleteButton setEnabled:(state&DEL)==DEL];

}


- (void) readDefaults
{
	const char	*defBuffer;
	
	defBuffer = NXReadDefault("Bibliography", "FieldConversion");
	if( defBuffer==NULL ) fieldConversion = 0;
	else fieldConversion = atoi( defBuffer );

	defBuffer = NXReadDefault("Bibliography", "EntryConversion");
	if( defBuffer==NULL ) entryConversion = 0;
	else entryConversion = atoi( defBuffer );

	defBuffer = NXReadDefault("Bibliography", "Remove");
	if( defBuffer==NULL ) removeTemp = YES;
	else removeTemp = defBuffer[0]=='Y';
}


//	set defaults from radio buttons

- fieldFormatButton:sender
{
	char fBuf[4];

	fieldConversion = [fieldRadio selectedRow];
	fBuf[0] = '0' + fieldConversion;
	fBuf[1] = '\0';
	NXWriteDefault("Bibliography", "FieldConversion", fBuf );
	return self;
}


- entryFormatButton:sender
{
	char fBuf[4];

	entryConversion = [entryRadio selectedRow];
	fBuf[0] = '0' + entryConversion;
	fBuf[1] = '\0';
	NXWriteDefault("Bibliography", "EntryConversion", fBuf );
	return self;
}



/*	====
	the configuration is saved in ~/Library/Bibliography/config
	====	*/


- init
{
	int		index;
	char	buffer[1024];
	passwd	*userData;
	
	STRING = NXUniqueString( "STRING" );
	PREAMBLE = NXUniqueString( "PREAMBLE" );
	COMMENT = NXUniqueString( "COMMENT" );

	[super init];

	// create and make the temporary directory
		sprintf( buffer, "/tmp/BIB%08d", getpid() );
		tempDir = NXCopyStringBuffer(buffer);
		mkdir( (const char *)tempDir, 0755 );

	// create  and make the library
		sprintf( buffer, "%s/Library", NXHomeDirectory() );
		if( mkdir( (const char *)buffer, 0755 )<0 ){
			if( errno!=EEXIST ){
				strcat(buffer, "  %s" );
				[self alert:buffer :strerror(errno)];
			}
		}
		sprintf( buffer, "%s/Library/Bibliography", NXHomeDirectory() );
		libDir = NXCopyStringBuffer(buffer);
		if( mkdir( (const char *)buffer, 0755 )<0 ){
			if( errno!=EEXIST ){
				strcat(buffer, "  %s" );
				[self alert:buffer :strerror(errno)];
			}
		}
		
	//	configuration file
		sprintf( buffer, "%s/config", libDir );
		configurationFile = NXCopyStringBuffer(buffer);	
	// shelf file
		sprintf( buffer, "%s/shelf", libDir );
		shelfFile = NXCopyStringBuffer(buffer);	
	// TeX template file
		sprintf( buffer, "%s/texTemplate.tex", libDir );
		templateFile = NXCopyStringBuffer(buffer);

	// read defaults from database
		[self readDefaults];
	
	// set the shell
		userData = (passwd *)getpwuid( getuid() );
		theShell = NXCopyStringBuffer(userData->pw_shell);

	// set the editor
		sprintf( buffer, "Edit" );
		theEditor = NXCopyStringBuffer(buffer);

	// set the style
		sprintf( buffer, "alpha" );
		theStyle = NXCopyStringBuffer(buffer);

	[self parse];	// fill entryNameList and configList

	// special entries
	
	[entryNameList addObject:(id)STRING];
	[entryNameList addObject:(id)PREAMBLE];
	[entryNameList addObject:(id)COMMENT];

	popUpList = [[PopUpList alloc] init];
	for( index=0; index<[entryNameList count]; index++ ){
		const char *name = (const char *)[entryNameList objectAt:index];
		if( name==STRING ) continue;
		if( name==PREAMBLE ) continue;
		if( name==COMMENT ) continue;
		[popUpList addItem:name];
	}

	[[showBox window] useOptimizedDrawing:YES];
	inspector = [BibTexView new];
	tableViewIsLoaded = NO;

if( toggleList[0].entryIndex==0 ){
	NXRunAlertPanel("Bibliography",
		"Your configurationfile is not uptodate,\n"
		"   (You cannot toggle Author<Ð>Editor,)\n"
		"Erase ~/Library/Bibliography/config\n",
			"  OK ",	// 1: default button
			NULL,		// 0: alternate
			NULL 		//-1: other
			);

}

	if( [self canExecuteProgram:"bibtex"]==NO ){
		[self alert:"you cannot execute %s" :"bibtex"];
	}
	if( [self canExecuteProgram:"latex"]==NO ){
		[self alert:"you cannot execute %s" :"latex"];
	}

	return self;
}


- (void)outMode:(FILE *)stream :(const char *)name :(int)m
{
	unsigned int tgl;

	fprintf( stream, "%s %c", name, (m&SHOW)? '+' : '-' );
	if( m&LARGE )fprintf( stream, "L" );
	fprintf( stream, "%c", "XROC"[m&3] );
	if( tgl=((m>>8)&0xff) )fprintf( stream, "T%c", tgl );
}


- (char *)timeStamp
{
	time_t	timeptr = time( NULL );
	return( asctime( localtime( &timeptr ) ) );
}


//	called from a button
- saveConfiguration:sender
{
	int		i, j;
	FILE	*fileStream = NULL;
	configListType	item;
	int		mode;
	BOOL	out;

	if( configurationFile &&
		(fileStream = fopen( configurationFile, "w+" ) )==NULL ){
			[self alert:"cannot open configuration file %s"
					   :configurationFile];
		return self;
	}

	fprintf( fileStream, "\n\n%%%%%%generated by Bibliography.app\n" );
	fprintf( fileStream, "%%%%%%\t\t%s\n", [self timeStamp] );
	fprintf( fileStream, "%%%%%%handle with care\n\n" );

	for( i=0; i<[entryNameList count]; i++ ){	// for all entryNames
		if( (NXAtom)[entryNameList objectAt:i]==STRING ) continue;
		if( (NXAtom)[entryNameList objectAt:i]==PREAMBLE ) continue;
		if( (NXAtom)[entryNameList objectAt:i]==COMMENT ) continue;
		fprintf( fileStream, "\n%s %c\n\t%c",
			(char *)[entryNameList objectAt:i], LBRACE, LBRACE );
		out = NO;
		for( j=0; j<[configList count]; j++ ){	// for required fieldNames
			item = *((configListType*)[configList elementAt:j]);
			mode = item.mode[i];
			if( (mode&3)==REQUIRED ){
				if( out ) fprintf( fileStream, ",  " );
				[self outMode:fileStream :item.fieldName :mode];
				out = YES;
			}
		}
		fprintf( fileStream, "%c\n\t%c", RBRACE, LBRACE );

		out = NO;
		for( j=0; j<[configList count]; j++ ){	// for optional fieldNames
			item = *((configListType*)[configList elementAt:j]);
			mode = item.mode[i];
			if( (mode&3)==OPTIONAL ){
				if( out ) fprintf( fileStream, ",  " );
				[self outMode:fileStream :item.fieldName :mode];
				out = YES;
			}
		}
		fprintf( fileStream, "%c\n\t%c", RBRACE, LBRACE );

		out = NO;
		for( j=0; j<[configList count]; j++ ){	// for custom fieldNames
			item = *((configListType*)[configList elementAt:j]);
			mode = item.mode[i];
			if( (mode&3)==CUSTOM ){
				if( out ) fprintf( fileStream, ",  " );
				[self outMode:fileStream :item.fieldName :mode];
				out = YES;
			}
		}
		fprintf( fileStream, "%c\n", RBRACE );

		fprintf( fileStream, "%c\n", RBRACE );
	}
	fclose( fileStream );

	NXWriteDefault("Bibliography", "FieldConversion", "2");
	NXWriteDefault("Bibliography", "EntryConversion", "2");
	NXWriteDefault("Bibliography", "Remove", removeTemp?"YES":"NO");
	return self;
}


- (void)shelfSettings
{
	int	i, row;

	theShelf = [[NXApp delegate] theShelf];
	if( theShelf==nil ) return;

	[shelfMatrix renewRows:0 cols:1];
	for(i=0, row=0; i<[theShelf count]; i++){
		const char *fn = [theShelf filenameOfIconAt:i];
		id	cell;
		[shelfMatrix addRow];
		cell = [shelfMatrix cellAt:row :0];
		if( fn ){
			[cell setTitle:fn];
			[cell setState:[theShelf getstateOfIconAt:i]];
			[cell setEnabled:YES];
		}
		else{
			[cell setTitle:"  <empty>"];
			[cell setEnabled:NO];
		}
		row++;

	}
	[shelfMatrix sizeToCells];
}


- nextShell:sender
{
	char *sh = getusershell();

	if( access("/usr/bin/bibtex", F_OK)<0 ){
		[self alert:"cannot access %s" :"bibtex"];
		//return self;
	}


	if( sh==NULL ){
		setusershell();
		sh = getusershell();
	}
	if( access(sh, F_OK)<0 ){
		[self alert:"the shell %s does not exist" :sh];
		return self;
	}
	if( access(sh, X_OK)<0 ){
		[self alert:"the shell %s is not executable" :sh];
		return self;
	}
	if( theShell ) free( (char *)theShell );
	theShell = NXCopyStringBuffer( sh );
	[defaultForm setStringValue:theShell at:2];
	return self;
}


- shelfDidChange
{
	[self shelfSettings];
	[[showBox window] display];
	return self;
}


- saveShelf:sender
{
	int	i, rows, cols;
	FILE	*fileStream = NULL;

	if( shelfFile &&
	  (fileStream = fopen( shelfFile, "w+" ) )==NULL ){
		[self alert:"cannot open shelf %s" :shelfFile];

		return nil;
	}

	fprintf( fileStream, "\n\n%%%%%%  shelf generated by Bibliography.app\n" );
	fprintf( fileStream, "%%%%%%\t\t%s\n", [self timeStamp] );
	fprintf( fileStream, "%%%%%%  handle with care\n\n" );

	[shelfMatrix getNumRows:&rows numCols:&cols];

	for(i=0; i<rows; i++){
		id	cell = [shelfMatrix cellAt:i :0];
		fprintf( fileStream, "\"%s\"   %c \n",
			[cell title], "-+"[[cell state]] );
	}
	fclose( fileStream );
	return self;
}


//	valid formats are:
//	0 : no conversion
//	1 : abcde
//	2 : ABCDE
//	3 : Abcde
- (void)convert:(char *)txt mode:(int)mode
{
	char	c;
	BOOL	first=YES;

	if( mode==0 ) return;

	while( c=*txt ){
		switch( mode ){
			case 0:	break;	// no conversion
			case 1:	if( NXIsUpper(c) ) *txt = NXToLower(c); break;
			case 2:	if( NXIsLower(c) ) *txt = NXToUpper(c); break;
			case 3: if( first ){
						if( NXIsLower(c) ) *txt = NXToUpper(c);
						first = NO;
					}
					else
						if( NXIsUpper(c) ) *txt = NXToLower(c);
					break;
			default: break;
		}
		txt++;
	}
}


// a (hopefully) new field is added to configList

- (void)addCustomField:(char *)name
{
	int	i;
	configListType	item;

	// new fieldname
	for( i=0; i<MODESIZE; i++ ) item.mode[ i ] = CUSTOM | SHOW;

	[self convert:name mode:fieldConversion];
	item.fieldName = NXUniqueString( name );
	[self fieldNameList];	// it really now exists
	[configList addElement:&item];	// not sorted
	[fieldNameList addObject:(id)item.fieldName];
	
	if( tableViewIsLoaded){
		[[showBox window] disableDisplay];
		[tableView addRow:(id)item.fieldName
				withTitle:item.fieldName];
		[tableView rowsChangedFrom:rowCount to:rowCount];
		[tableView setRowHeadingVisible:YES];
		rowCount++;
		[tableView scrollRowToVisible:rowCount];
		[[showBox window] reenableDisplay];
	}
	if( prefPanel ){
		[self showOptionsFor:entryIndex];
	}
	[inspector prefsDidChange];
}


//	activated from 'ADD' - button

- addFieldName:sender
{
	int	inx;
	
	inx = [self indexOfFieldname:[fieldNameField stringValue]];
	
	if( inx>=0 ) return self;	// name already exists
	if( ([fieldNameField stringValue]==NULL)
	  || [fieldNameField stringValue][0]=='\0' ) return self;

	[self addCustomField:(char *)[fieldNameField stringValue]];
	[addButton setEnabled:NO];
	return self;
}


- changeFieldname:sender	// geht nicht
{
	int	inx;
	
	inx = [self indexOfFieldname:[fieldNameField stringValue]];

	if( inx<0 ) return self;	// name does not exists
	fprintf( stderr, "index of %s is %d\n",
		[fieldNameField stringValue], inx );
	fprintf( stderr, "changeEntryname \n" );
	return self;
}



- deleteFieldname:sender
{
	int	inx;
	configListType	*item;
	inx = [self indexOfFieldname:[fieldNameField stringValue]];
	
	if( inx<0 ) return self;	// name does not exists

	item = ((configListType*)[configList elementAt:inx]);
	//fprintf( stderr, "%d %s  %d\n", inx, item->fieldName, (item->mode[0]&3));
	if( (item->mode[0]&3) != CUSTOM ){	// do not delete ! @@@
		if( BIBTEST ) fprintf( stderr, "do not delete\n" );
		return self;
	}
	
	[[showBox window] disableDisplay];
		[configList removeElementAt:inx];
		[fieldNameList removeObjectAt:inx];
		[tableView rowAt:inx];
		//[[tableView rowAt:inx] free];
		[tableView removeRowAt:inx];
		[tableView rowsChangedFrom:0 to:rowCount];
		rowCount--;
		[tableView setRowHeadingVisible:YES];
		[tableView scrollRowToVisible:rowCount-1];
	[[showBox window] reenableDisplay];
	[tableView display];
	[inspector prefsDidChange];
	return self;
}


//	sets the mode "m" for the fieldname "name" at index 'actual entryType'
//	if the field exists it is changed
//	otherwise a new field is added to configList
//	called from readConfigList

- addField:(NXAtom)name mode:(unsigned short)m
{
	int	i;
	configListType	*item, newItem;
	int	inx = [entryNameList count] - 1;	// index of actual (??) entryType

	for( i=0; i<[configList count]; i++ ){	// new fieldname ?
		item = ((configListType*)[configList elementAt:i]);
		if( (item->fieldName)==name ){
			item->mode[ inx ] = m;
			return self;
		}
	}
	// new fieldname
	for( i=0; i<MODESIZE; i++ ) newItem.mode[ i ] = 0;
	newItem.fieldName = name;
	newItem.mode[ inx ] = m;
	[configList addElement:&newItem];
	[inspector prefsDidChange];
	return self;
}

- (void) nextToken
{
	if( token=='\n' ){
		lineNumber++;
	}
	token = NXGetc( configStream );
}

- (void) skip
{
	while( NXIsSpace(token) || (token=='%') ){
		if( token=='%' ){	// skip to end of line
			while( (token!='\n') && (token!=EOF) ) [self nextToken];
		}
		else [self nextToken];
	}
}



- (NXAtom) getEntryName
{
	char	buf[128];
	int		i = 0;
	while( NXIsAlpha(token) ){
		buf[i] = token;
		i++;
		[self nextToken];
	}
	buf[i] = '\0';
	[self convert:buf mode:entryConversion];
	return NXUniqueString( (const char *)buf );
}


#define isOneOfTheTen(x)	(x=='"' || x=='#' || x=='%' || x=='\''\
	 || x==LPARA || x==RPARA || x==',' || x=='=' || x==LBRACE || x==RBRACE)
	 

- (NXAtom) getFieldName
{
	char	buf[128];
	int		i = 0;
	while( !isOneOfTheTen(token) && token!=EOF && !NXIsSpace(token) ){
		buf[i] = token;
		i++;
		[self nextToken];
	}
	buf[i] = '\0';
	[self convert:buf mode:fieldConversion];
	return NXUniqueString( (const char *)buf );
}



// precondition: token = LBRACE

- (void) readConfigList
{
	unsigned int mode=0;
	int		j;
	NXAtom	fieldName;

	[self nextToken];	// skip LBRACE
	[self skip];
	while( token!=EOF ){
		fieldName = [self getFieldName];
		// read mode
		while( (token!=',') && (token!=RBRACE) && (token!=EOF)){
			[self skip];
			mode = 0;
			while( (token!=',') && (token!=RBRACE) && (token!=EOF)){
				switch( token ){
					case '+' : mode = SHOW; break;
					case '-' : break;
					case 'R' : mode |= REQUIRED; break;
					case 'O' : mode |= OPTIONAL; break;
					case 'C' : mode |= CUSTOM; break;
					case 'L' : mode |= LARGE; break;
					case 'T' : mode |= (NXGetc( configStream ))<<8;
						j = [self findIndexFor:[entryNameList count]-1
											  :(mode>>8)];
						[self insert:fieldName at:j];
						break;
					default : break;
				}
				[self nextToken];
			}
			[self addField:fieldName  mode:mode];
			//[self nextToken];
		}
		if( token==',' ){
			[self nextToken];	// skip ','
			[self skip];
		}
		else
		if( token==RBRACE ){
			[self nextToken];	// skip RBRACE'
			break;
		}
	}
	// postcondition = first char after (matching) RBRACE
}


static char *titles[] = {"mode", "show", "size"};


- setUpTableView
{
	int		i;

	rowCount = 0;
	columnCount = 3;

	[tableView setRowHeadingVisible:YES];
	[tableView setColumnHeadingVisible:YES];
	[tableView setDataSource:self];
	[tableView setTarget:self];
	[tableView setDelegate:self];
	[tableView setAction:@selector(tableViewSingleClick:)];
	[tableView setDoubleAction:@selector(tableViewDoubleClick:)];
	
	[tableView setGridVisible:YES];
	[tableView setEditable:NO];
	[tableView allowVectorReordering:YES];
	[tableView allowVectorResizing:YES];
	[tableView removeColumnAt:0];
	[tableView removeColumnAt:1];

 	for(i=0; i<columnCount; i++ ){
		[tableView addColumn:(id)i withTitle:titles[i]];
		[[tableView columnAt:i] setMinSize:45];
		[[tableView columnAt:i] setMaxSize:90];
	}

	return self;
}


- makeTableView
{
	int	index;

	for( index=0; index<[configList count]; index++ ){
		configListType	*item;
		item  =((configListType*)[configList elementAt:index]);
		[tableView addRow:(id)item->fieldName withTitle:item->fieldName];
		rowCount++;
	}

	return self;
}


- makeTabMatrix
{
	NXRect	backgroundRect,matrixRect;
	TabSelectionCell *aCell;
	unsigned int entryFormMask = [tabCellView autosizing];	//€as set in IB

	/* Set tabCellView to be flipped ('cause Matrices are flipped) */
	[tabCellView setFlipped:YES];
	
	/* Get the tabCellView's dimensions */
	[tabCellView getBounds:&backgroundRect];

	/* Set up the matrix bounds */
	matrixRect = backgroundRect;
	matrixRect.origin.y = NX_MAXY(&matrixRect) - 17.0;

	/* Prepare a matrix to go inside the tabCellView */
	tabMatrix = [[Matrix allocFromZone:[self zone]] initFrame:&matrixRect 
						mode:NX_RADIOMODE
						cellClass:[TabSelectionCell class]
						numRows:1
						numCols:5];
	
	/* Set the background gray of tabMatrix to NX_DKGRAY */
	[tabMatrix setBackgroundGray:NX_DKGRAY];
	
	/* Set the autosizing and autoscrolling attributes of the matrix */
	[tabMatrix setAutosizing:entryFormMask];
	//[tabMatrix setAutoscroll:YES];
	[tabMatrix setAutosizeCells:YES];

	/* Stick the matrix in our scrollView */
	[tabCellView addSubview:tabMatrix];
	[tabCellView setAutoresizeSubviews:YES];
    [tabCellView setAutosizing:entryFormMask];

	/* Set our target and single-click actions */
	[tabMatrix setTarget:self];
	[tabMatrix setAction:@selector(tabMatrixAction:)];
	aCell = [tabMatrix cellAt:0 :0];
	[aCell initTextCell:"entries"];
	aCell = [tabMatrix cellAt:0 :1];
	[aCell initTextCell:"shelf"];
	aCell = [tabMatrix cellAt:0 :2];
	[aCell initTextCell:"styles"];
	aCell = [tabMatrix cellAt:0 :3];
	[aCell initTextCell:"general"];
	aCell = [tabMatrix cellAt:0 :4];
	[aCell initTextCell:"citation"];

	/* Size tabMatrix to its cells and display it */
	[tabMatrix sizeToCells];
	[tabMatrix display];

	return self;
}


- awakeFromNib
{
	entryView = [entryView contentView];
	shelfView = [shelfView contentView];
	filenameView = [filenameView contentView];
	styleView = [styleView contentView];
	expertView = [expertView contentView];

	[shelfMatrix setTarget:self];
	[shelfMatrix setAction:@selector(openOnLaunch:)];
	[prefPanel setFrameUsingName:"PreferencePanel"];
	[prefPanel setFrameAutosaveName:"PreferencePanel"];

	[addButton setEnabled:NO];
	[renameButton setEnabled:NO];
	[deleteButton setEnabled:NO];
	[fieldNameField setStringValue:""];
	
	//	default buttons
	[fieldRadio  selectCellAt:fieldConversion :0];
	[entryRadio  selectCellAt:entryConversion :0];
	[removeTempButton setState:removeTemp==YES];

	// style browser
	[styleBrowser setDelegate:self];
	[styleBrowser setAction:@selector(styleSingleClick:)];
	[styleBrowser setDoubleAction:@selector(styleDoubleClick:)];
	[styleBrowser setMinColumnWidth:22];
	[styleBrowser setMaxVisibleColumns:2];
	[styleBrowser setTitle:"TEXINPUTS" ofColumn:0];

	//	prepare the tabCells
	[self makeTabMatrix];

    /* other setup code */
    return self;
}


- tabMatrixAction:sender
{	
	[[showBox window] disableDisplay];
	switch( [sender selectedCol] ){
		case 0: [showBox setContentView:entryView]; break;
		case 1: [showBox setContentView:shelfView]; break;
		case 2: [showBox setContentView:styleView];
				[styleBrowser loadColumnZero];
				break;
		case 3: [showBox setContentView:filenameView]; break;
		case 4: [showBox setContentView:expertView]; break;
		default : break;
	}
	[[showBox window] reenableDisplay];
	[showBox display];
	return self;
}


- (void) loadDefault
{
	char buf[MAXPATHLEN];
	id	m = [NXBundle mainBundle];

	// load configuration file from the bundle
	if( [m getPath:buf forResource:"Configuration" ofType:""] ){
		configStream = NXMapFile(buf, NX_READONLY);
	}
	else {
		[self alert:"cannot load default file\n%s" :"Configuration"];
		[self alert:
				"I am helpless without configuration"
				"\nApplication will abort"// message
				: NULL ];
		exit( 77 );
	}
}


- parse
{
	char	buf[64];
	lineNumber = 1;
	sprintf( buf, "{%%ii[%dS]}", MODESIZE );
	configList = [[Storage alloc]
					initCount:0
					elementSize:sizeof(configListType)
					description:buf];
	configStream = NXMapFile( configurationFile, NX_READONLY); 
	if( configStream==NULL ) [self loadDefault];

	entryNameList = [[List alloc] initCount:20];
	NXSeek( configStream, 0, NX_FROMSTART ); // position
	[self nextToken];	// first token is now defined
	while( token!=EOF ){
		NXAtom	theName;
		[self skip];
		if( token==EOF ) break;
		if( !NXIsAlpha(token) ){
			fprintf( stderr, "error in line %d (char '%c' = %d)\n",
				lineNumber, token, token );
			break;
		}
		theName = [self getEntryName];
	// STRING is not allowed
		if( ! NXOrderStrings( theName, "STRING", NO, -1, NULL ) ){
			[self alert:
				"STRING not allowed in preferences"
				"\nchange (or delete) your file"
				"       %s/Library/Bibliography/config"
				"\nApplication will abort"// message
				: NXHomeDirectory() ];
			exit( 77 );
		}

	// PREAMBLE is not allowed
		if( ! NXOrderStrings( theName, "PREAMBLE", NO, -1, NULL ) ){
			[self alert:
				"PREAMBLE not allowed in preferences"
				"\nchange (or delete) your file"
				"       %s/Library/Bibliography/config"
				"\nApplication will abort"// message
				: NXHomeDirectory() ];
			exit( 77 );
		}

	// COMMENT is not allowed
		if( ! NXOrderStrings( theName, "COMMENT", NO, -1, NULL ) ){
			[self alert:
				"COMMENT not allowed in preferences"
				"\nchange (or delete) your file"
				"       %s/Library/Bibliography/config"
				"\nApplication will abort"// message
				: NXHomeDirectory() ];
			exit( 77 );
		}

		[entryNameList addObject:(id)theName];
		[self skip];
		if( token!=LBRACE ){
			fprintf( stderr, "syntax error in line %d (char '%c' = %d)",
				lineNumber, token, token );
			break;
		}
		[self nextToken];	// skip LBRACE
		[self skip];
		if( token==RBRACE ) continue;	// empty entry
		while( token==LBRACE ){
			[self readConfigList];
			[self skip];
		}
		if( token!=RBRACE ){
			NXRunAlertPanel("Bibliography",
				"syntax error in line %d (char '%c' = %d) of file %s",
				"  OK ",	// 1: default button
				NULL,		// 0: alternate
				NULL, 		//-1: other
				lineNumber, token, token, configurationFile
				);

			if(0)fprintf( stderr, "syntax error in line %d (char '%c' = %d)",
				lineNumber, token, token );
			break;
		}
		[self nextToken];	// skip RBRACE
		[self skip]; 
	}

	NXCloseMemory( configStream, NX_FREEBUFFER );

	return self;
}


//	[NXApp delegate] calls this method to show the panel
//	comes from menu Info -> Preferences
- show
{	
	if( prefPanel ){	// not first call
		[prefPanel makeKeyAndOrderFront:self];
		return self;
	}
	
	// first call
	[NXApp loadNibSection:"preferences.nib" owner:self];
	if( tableViewIsLoaded==NO ){	
		[[showBox window] disableDisplay];
		[self setUpTableView];
		[self makeTableView];
		tableViewIsLoaded=YES;
		[[showBox window] reenableDisplay];
	}
	[self showOptionsFor:entryIndex];
	[self shelfSettings];
	[prefPanel makeKeyAndOrderFront:self];
	[fieldNameField setEnabled:YES];
	[fieldNameField selectText:self];
	[defaultForm setStringValue:tempDir at:0];
	[defaultForm setStringValue:"~/Library" at:1];	// default files
	[defaultForm setStringValue:theShell at:2];	// favorite shell
	[defaultForm setStringValue:theEditor at:3];	// favorite editor
	[defaultForm setStringValue:theStyle at:4];	// default style

	return self;
}


- (const char *)theShell
{
	return theShell;
}


- (const char *)theEditor
{
	if( !prefPanel ){	// take the default
		return theEditor;
	}
	return 	[defaultForm stringValueAt:3];	// favorite editor
}


- (const char *)theStyle
{
	return theStyle;
}


- (BOOL) canExecuteProgram:(const char *)prog
{
	int	i;
	char buffer[2048];
	char thePath[1024];
	List *path = [self shellArgsOf:"PATH" toBuffer:buffer size:2048];
	
	if( path==nil ) return NO;
	
	for( i=0; i<[path count]; i++ ){
		sprintf( thePath, "%s/%s", (char *)[path objectAt:i], prog );
		if( access(thePath, X_OK)<0 ) continue;
		[path free];
		return YES;
	}
	[path free];
	return NO;
}


- bibPopup:sender	// sender is Matrix
{
	entryIndex = [sender selectedRow];
	[self showOptionsFor:entryIndex];
	return self;
}


- (int)citeMode
{
	int	mode;

	if( [[NXApp delegate] entryDragMethod]==1 ){
		return 3;	// drag entries as text
	}
	if( prefPanel ){
		mode = [[[[citeStyleButton target] itemList] selectedCell] tag];
		return (mode<1)? 1 : mode;
	}
	return 1;
}


- (const char *) citeStyle
{
	if( prefPanel )	return [citeStyleButton title];
	return "\\c{...}, \\c{...}";
}


- (const char *) citeText
{
	if( prefPanel )	return [citeTextButton title];
	return "\\cite";
}


- citationStyle:sender
{
	return self;
}


- setCitationPopUpList:(const char *)theString
{
	PopUpList	*citeTextPopUpList = [citeTextButton target];

	if( [citeTextPopUpList indexOfItem:theString]>=0 ) return self;

	[citeTextPopUpList insertItem:theString
		at:[[citeTextButton target] count]-1];
	[citeTextButton setTitle:theString];
	return self;
}


- citationText:sender
{
	if( [[sender selectedCell] tag]==4 ){
		[self perform:@selector(setCitationPopUpList:)
			with:(id)[customCiteText stringValue]
			afterDelay:0 cancelPrevious:YES];
	}
	return self;
}


- setCustomcite:sender
{
	[self setCitationPopUpList:[customCiteText stringValue]];
	return self;
}


- removeTempButton:sender
{	
	removeTemp = [sender state]==1;
	NXWriteDefault("Bibliography", "Remove", removeTemp?"YES":"NO");
	return self;
}


- (BOOL)shallRemoveTempFiles
{
	return removeTemp;
}


- openOnLaunch:sender
{
	[theShelf setState:[[sender selectedCell] state]
			 forIconAt:[sender selectedRow]];
	return self;
}


- (int)indexOfIdentifier:(NXAtom)ident
{
	int	i;
	
	if( (ident==NULL) || (*ident=='\0') ) return -1;
	[self fieldNameList];
	for( i=0; i<[fieldNameList count]; i++ ){
		if( (NXAtom)[fieldNameList objectAt:i]==ident) return i;
		// index in fieldNameList == index in configList
	}
	return -1;
}


//	row and col are the `physical' values
//	they must be converted to the `logical' rows and cols
- (BOOL)changeValueAt:(int)row :(int)col
{
	int	i;
	unsigned int bit;
	configListType	*item;
    id	<DBTableVectors> rowVector;
    id	<DBTableVectors> colVector;

	if( row<0 ) return NO;	// nothing selected
	if( col<0 ) return NO;	// nothing selected

    rowVector = [tableView rowAt:row];
    colVector = [tableView columnAt:col];

	row = [self indexOfIdentifier:(NXAtom)[rowVector identifier]];
	col = (unsigned)[colVector identifier];

	item = ((configListType*)[configList elementAt:row]);
	switch( col ){
		case 0: return NO;	// req etc. cannot be changed

		case 1: item->mode[entryIndex] ^= SHOW;	// SHOW <-> NotShow
				if( [validButton state] ){
					bit = item->mode[entryIndex] & SHOW;
					for( i=0; i<MODESIZE; i++ ){
						if( bit ) item->mode[i] |= SHOW;
						else	  item->mode[i] &= ~SHOW;
					}
				}
				break;

		case 2:	item->mode[entryIndex] ^= LARGE;	// LARGE <-> SMALL
				if( [validButton state] ){
					bit = item->mode[entryIndex] & LARGE;
					for( i=0; i<MODESIZE; i++ ){
						if( bit ) item->mode[i] |= LARGE;
						else      item->mode[i] &= ~LARGE;
					}
				}

				break;

		default: break;
	}
	return YES;
}


- tableViewSingleClick:sender	// sender is matrix
{
	int	x;
	int	col;
	int	row = [sender selectedRow];
	NXRect	aRect;
	View *rowHead = [tableView rowHeading]; 
	NXEvent event;
    NXPoint point;

	if( NXGetOrPeekEvent(
			[NXApp context], &event, NX_MOUSEUPMASK, 0.0, 22, 1) ){
    	point = event.location;
		//	convert the coordinate of point to the window
		[rowHead convertPoint:&point fromView:nil];

		[rowHead getVisibleRect:&aRect];
		//	x is leftmost position of the mouseclick (window coordinates)
		x = (int)(aRect.origin.x + aRect.size.width) + 2;
		// search for the selected column		
		col = 0;
		while( ((int)point.x > x) && (col<=2) )
			x += (int)[[tableView columnAt:col++] size] + 2;
		col--;
		if( col<0 ) col=0;
		//	row and col are the `physical' values
		if( [self changeValueAt:row :col] ){
			[tableView rowsChangedFrom:row to:row];
			[inspector prefsDidChange];
		}
	}
	else{
		[self fieldNameList];
		[fieldNameField
			setStringValue:
				(const unsigned char *)[fieldNameList objectAt:row]];
		[self setButtonsFor:(char *)[fieldNameField stringValue]];
	}
	return self;
}


- tableViewDoubleClick:sender	// sender is DBTableView
{
	int	row = [sender selectedRow];
	int	col = [sender selectedColumn];
	NXEvent event;
    NXPoint point;

return self;

	if( NXGetOrPeekEvent(
			[NXApp context], &event, NX_MOUSEUPMASK, 0.0, 22, 1) ){
    	point = event.location;
		fprintf( stderr,
			"double at (%d,%d) -> (%d,%d)\n",
			row, col, (int)point.x, (int)point.y );
		fprintf( stderr, "selRow:%d\n", [tableView selectedRowAfter:0] );
		fprintf( stderr, "selCol:%d\n", [tableView selectedColumnAfter:0] );
		fprintf( stderr, "sender is a %s\n", [sender name] );
		fprintf( stderr, "rowlist %d\n", [[tableView rowList] count] );
		fprintf( stderr, "is of type %s\n",
			[[[tableView rowList] objectAt:0] name] );
		if(0)fprintf( stderr, "selected cell is a %s\n",
			[[sender selectedCell] name] );
	}
	else
	fprintf( stderr, "double at %d %d\n", row, col );
		
	return self;
}


- entryNameList
{
	return entryNameList;
}


- configList
{
	return configList;
}


- fieldNameList
{
	if( fieldNameList==nil ){
		int	i;
		configListType	*item;

		fieldNameList = [[List alloc] initCount:32];
		for( i=0; i<[configList count]; i++ ){
			item = ((configListType*)[configList elementAt:i]);
			[fieldNameList addObject:(id)(item->fieldName)];
		}
	}
	return fieldNameList;
}


- (int)indexOfFieldname:(const char *)name
{
	int	i;
	
	if( (name==NULL) || (*name=='\0') ) return -1;
	[self fieldNameList];
	for( i=0; i<[fieldNameList count]; i++ ){
		if( ! NXOrderStrings(
				(const unsigned char *)[fieldNameList objectAt:i],
				(const unsigned char *)name,
				NO, -1, NULL )
			)return i;	// index in fieldNameList == index in configList
	}
	return -1;
}


- (int)indexOfEntryname:(const char *)name
{
	int	i;
	
	for( i=0; i<[entryNameList count]; i++ ){
		if( ! NXOrderStrings(
				(const unsigned char *)[entryNameList objectAt:i],
				(const unsigned char *)name,
				NO, -1, NULL )
			)return i;	// index in entryNameList
	}
	return -1;
}


- (const char *)tempDirectory
{
	return tempDir;
}


- (const char *)libDirectory
{
	return libDir;
}


- (const char *)templateFile
{
	return templateFile;
}


- (const char *)shelfFile
{
	return shelfFile;
}


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

- (BOOL)updateMenuFor:menuCell
{
	BOOL	newState=NO;	// .. and the compiler keeps silent

	switch( [menuCell tag] ){
		case CUT			:
		case COPY			:
		case PASTE			:
		case SELECTALL		:
		case ENTERSELECTION : newState = YES; break;

		case PRVWINDOW		: newState = YES; break;	// why ?
		default : newState = [[NXApp delegate] updateMenuFor:menuCell];
				  break;
		}

	return newState;
}


- textDidGetKeys:sender isEmpty:(BOOL)flag
{
	[self setButtonsFor:(char *)[fieldNameField stringValue]];
	return self;
}


- restoreEditorName
{
	[defaultForm setStringValue:theEditor at:3];	// restore
	return self;
}


- (BOOL)textWillEnd:sender
{
	int	row;
	const char* edt;
	if( [[sender delegate] tag]!=77 ) return NO;

	row = [[sender delegate] selectedIndex];
	edt = [[Application workspace] getFullPathForApplication:
		 		[[sender delegate] stringValueAt:row]];
	if( edt==NULL ){
		[self alert:"cannot find application %s"
				   :[[sender delegate] stringValueAt:row]];
		[self perform:@selector(restoreEditorName)
			with:(id)nil afterDelay:0 cancelPrevious:YES];
	}
	return NO;
}


/*======================================================================
 *	DBTableView data source (pseudo-delegate) methods
 *======================================================================*/

- (unsigned int)rowCount
{
	fprintf( stderr, "rowCount %d\n", rowCount );
    return rowCount;
}

- (unsigned int)columnCount
{
	fprintf( stderr, "columnCount\n" );
    return 3;
}

/* DBTV sends this message when it needs to know what to display */
- getValueFor:identifier at:(unsigned int)aPosition into:aValue
{
 /*
  * note that when loaded, we set the identifiers to be the locations of the
  * configuration objects in the configuration list 
  */
    fprintf( stderr, "getValueFor %d at %d\n", (int)identifier, aPosition );
    //[aValue setIntValue:(int)identifier];

	//	ignore this method, we use getValueFor::into:
    return nil;
}

/* DBTV sends this message when it needs to know what to display */
- getValueFor:rowIdentifier :colIdentifier into:aValue
{
	int	row = [self indexOfIdentifier:(NXAtom)rowIdentifier];
	int	col = (int)colIdentifier;
	int	m;
	configListType	*item;

	if( row<0 ) return nil;	// nothing selected
	if( col<0 ) return nil;	// nothing selected
	
	item = ((configListType*)[configList elementAt:row]);
	m = item->mode[entryIndex];
	//fprintf( stderr, "entryIndex is %d at %d %d\n", entryIndex, row, col );
	switch( col ){
		case 0 :
				switch( m & 3 ){
				case REQUIRED : [aValue setStringValue:"req"]; break;
	
				case OPTIONAL : [aValue setStringValue:"opt"]; break;
	
				case CUSTOM   : [aValue setStringValue:"custom"]; break;
	
				default		  : [aValue setStringValue:" - "]; break;
				}
				break;

		case 1 :
				if( m & SHOW )   [aValue setStringValue:"show"];
				else	if( m&3 )[aValue setStringValue:"hide"];
						else	 [aValue setStringValue:" - "];
				break;

		case 2 :
				if( m & LARGE )	[aValue setStringValue:"LARGE"];
				else			[aValue setStringValue:"SMALL"];
				break;

		default : break;
	}
    if(0)fprintf( stderr, "getValueFor (%d %d)\n", row, col );
    return nil;
}

/* DBTV sends this message when data in a cell has changed */
- setValueFor:identifier at:(unsigned int)aPosition from:aValue
{
    fprintf( stderr, "setValueFor\n" );
    return nil;
}


- setValueFor:rowIdentifier :columnIdentifier from:aValue
{
	fprintf( stderr, "setValueFor row=%d  col=%d\n",
		(int)rowIdentifier, (int)columnIdentifier );
	return nil;
}


//	browser delegate methods

#include <sys/dir.h>  /* POSIX applications #include <dirent.h> */


- (BOOL)isDirectory:(const char *)path
{
	struct	stat fBuf;
	int	rtn = stat(path, &fBuf);

	if( rtn<0 ){
		perror("stat error");
		return NO;
	}
	return (S_IFDIR & fBuf.st_mode) != 0;
}


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

- styleSingleClick:sender
{
	char	path[1024];
	char	*lastDot;
	int	selCol  = [sender selectedColumn];

	if( selCol==0 ) return self;
	[styleBrowser getPath:path toColumn:selCol+1];	// paths starts with "//"

	if( access(path, F_OK)<0 ){
		[self alert:"the stylefile %s does not exist" :path+1];
		return self;
	}
	if( access(path, R_OK)<0 ){
		[self alert:"the stylefile %s is not readable" :path+1];
		return self;
	}

	if( theStyle ) free( (char *)theStyle );
	lastDot = rindex( path, '.' );
	if( lastDot ){
		*lastDot = '\0';
	}
	theStyle = NXCopyStringBuffer(path+1);
	[styleText setStringValue:theStyle];
	if(0)[[Application workspace] openFile:(path+1)
		withApplication:[self theEditor]];
	return self;
}


- (List *)shellArgsOf:(const char *)para toBuffer:(char *)buf size:(int)size
{
	int		i=0;
	char	c, *str;
	FILE	*env;
	List	*args;

	sprintf( buf, "%s -c 'echo -n $%s' 2>/dev/null",
		[self theShell], para );
	env = popen( buf, "r" );
	str = fgets( buf, size-1, env );
	if (str==NULL ){ return nil; }

	args = [[List alloc] init];
	[args addObject:(id)buf];
	while( YES ){
		c = buf[i];
		if( (c==':') || (c=='\0') ){
			buf[i] = '\0';
			if( c=='\0' ) break;
			else [args addObject:(id)&buf[i+1]];
		}
		i++;
	}

	return args;
}


- (int)fillColZero:locMatrix
{
	int		i=0, row=0;
	id		newCell;
	char	buffer[1024];
	List *args = [self shellArgsOf:"TEXINPUTS" toBuffer:buffer size:1024];

	//[self canExecuteProgram:"bibtex"];
	//[self canExecuteProgram:"latex"];

	if (args==nil ){
		//fprintf( stderr, "Erorr: no variable TEXINPUTS\n" );
		args = [[List alloc] init];
		[args addObject:(id)"."];
		[args addObject:(id)"/usr/lib/tex/inputs"];	// default
	}

	for( i=0; i<[args count]; i++ ){
		const char *para = (const char *)[args objectAt:i];
		if( para[0]=='\0' ) continue;	// (empty string)
		
		[locMatrix addRow];
		newCell = [locMatrix cellAt:row :0];
		row++;
		if( strcmp(para, ".")==0 ){
			[newCell setStringValue:tempDir];
			//[newCell setStringValue:NXHomeDirectory()];
		}
		else{
			[newCell setStringValue:para];
		}
		[newCell setLeaf:NO];
		[newCell setLoaded:YES];
	}

	[args free];
	return row;
}


- (int)fillMatrix:locMatrix
{
	DIR		*dirp;
	struct	direct *dp;
	int		length;
	int		count = 0;
	id		newCell;
	char	path[1024];
	char	buffer[1024];

	////////return [[MacroDB new] fillMatrix:locMatrix];

	[styleBrowser getPath:path
		toColumn:[styleBrowser selectedColumn]+1];

	// path starts with "//", do not use the first character	
	dirp = opendir( path+1 );
	if( dirp==NULL ){
		sprintf( buffer, "%s\n%s", path+1, strerror(errno) );
		[self alert:"  %s" :buffer];
		return 0;
	}
	for( dp = readdir(dirp); dp != NULL; dp = readdir(dirp) ) {
		length = strlen( dp->d_name );
		if( length<=4 ) continue;
	// show also directories
		sprintf( buffer, "%s/%s", path+1, dp->d_name );
		if( NO && [self isDirectory:buffer] ){
			[locMatrix addRow];
			newCell = [locMatrix cellAt:count :0];
			[newCell setStringValue:dp->d_name];
			[newCell setLeaf:NO];
			[newCell setLoaded:YES];
			count++;
			continue;
		}
		if( dp->d_name[length-4]=='.' &&
			dp->d_name[length-3]=='b' &&
			dp->d_name[length-2]=='s' &&
			dp->d_name[length-1]=='t'
			){
				[locMatrix addRow];
				newCell = [locMatrix cellAt:count :0];
				[newCell setStringValue:dp->d_name];
				[newCell setLeaf:YES];
				[newCell setLoaded:YES];
				count++;
			}
	}
	closedir(dirp);

	return count;
}


- (int)browser:sender fillMatrix:locMatrix inColumn:(int)column
{
	//id	matrix = [theBrowser matrixInColumn:column-1];
	//int	index = [matrix selectedRow];

	if( column==0 ){
		return [self fillColZero:locMatrix];
	}
	return [self fillMatrix:locMatrix];
}

/*****		for experts only	********/

- (int)sortAlgorithm
{
	return 0;
}


- (int)cutOff
{
	return 50;
}


@end


