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


#import "Controller.h"
#import "BibTexView.h"
#import "BibTeXObject.h"
#import "BibliographicFile.h"
#import "Preferences.h"
#import "BufferClass.h"


#define	IN &

#define KEY_FIELD_TAG	100
#define NOTE_TEXT_TAG	120
#define FORM_FIELD_TAG	123

#define BIG_LEFT	NXPrintf( localStream, "%s",\
		[leftBraceButton state] ?\
		[leftBraceButton altTitle]: [leftBraceButton title] )

#define BIG_RIGHT	NXPrintf( localStream, "%s\n\n",\
		[rightBraceButton state] ?\
		[rightBraceButton altTitle]: [rightBraceButton title] )

#define	LEFTQUOTE	if( cooked ) NXPutc(localStream, LBRACE )
#define	RIGHTQUOTE	if( cooked ) NXPutc(localStream, RBRACE )

#define THE_TYPE	NXPrintf( localStream, "\n@%s",[popUpButton title] )

#define	THE_KEY		NXPrintf( localStream, " %s",\
		[self stringOf:[keyField stringValue]] )

#define	ASSIGN		NXPrintf( localStream, "\t= " )

#define ACTUALBUFFER	\
		[self convert:[self listBufferFor:actualRow] toStream:localStream]

#define	NEXTENTRY(title)	NXPrintf( localStream, ",\n\t%s\t= ", title )

#define	NEWLINE	NXPrintf( localStream, "\n" )

#define ADD_HASH_MARK { NXPrintf( toStream, " # "); }


static	id	entryInspectorObject = nil;

static	int	STRING;
static	int	PREAMBLE;
static	int	COMMENT;


static NXStream	*localStream = NULL;
static char		*localBuffer;
static int		locBufferLength, locBufferMaxLength;

static	id viewBuffer = nil; 
static	id cookedBuffer = nil;


@implementation BibTexView

+ new
{
	if( entryInspectorObject==nil ){
		entryInspectorObject = [[super alloc] init];
		viewBuffer = [[BufferClass alloc] initBufferSize:80];
		cookedBuffer = [[BufferClass alloc] init];
	}
	return entryInspectorObject;
}


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

- (void)initButtons
{
	int	i;
	id	button;
	NXRect	aFrame = { {0.0, 0.0}, {18.0, 18.0} };
	toggleListType *toggleList = [preferences toggleList];
	
	i = 0;
	while( toggleList[i].entryIndex ){	// create a button
	// create a new toggle button
		button = [[Button alloc] initFrame:&aFrame];
		[button setTitle:"t"];
		[button setTag:i];
		[button setTarget:self];
		[button setAction:@selector(toggle:)];

		[button setTransparent:YES];

		toggleList[i].tglButton = button;
		toggleList[i].isInWindow = NO;
		i++;
	}
}


- (void)clearToggleButtons
{
	int	i = 0;
	toggleListType *toggleList = [preferences toggleList];
	
	while( toggleList[i].entryIndex ){	// remove a button from superview
		if( toggleList[i].isInWindow==YES ){
			[toggleList[i].tglButton removeFromSuperview];
			toggleList[i].isInWindow = NO;
		}
		i++;
	}
}


- findCellWithTitle:(NXAtom)txt at:(int *)inx
{
	int	rows, cols;
	int	i, len = strlen(txt);

	[entryForm getNumRows:&rows numCols:&cols];
	for( i=0; i<rows; i++ ){
		if( NXOrderStrings([entryForm titleAt:i], txt, NO, len, NULL)==0 ){
			// fprintf( stderr, "found %s in %d\n", txt, i );
			*inx = i;
			return [entryForm cellAt:i :0];
		}
	}
	//fprintf( stderr, "not found %s\n", txt );	// %%%% ??? %%%%%
	*inx = -1;
	return nil;
}


- setDelayedToggleButtonsFor:(int)eType
{
	int	i=0, j;
	id	tglButton;
	id	theCell;
	NXRect	cellFrame;
	toggleListType *toggleList = [preferences toggleList];

	[self clearToggleButtons];

	while( toggleList[i].entryIndex ){
		if( toggleList[i].entryIndex==eType ){
			id fnt = [Font newFont:"Helvetica-BoldOblique" size:12.0 ];
			theCell = [self findCellWithTitle:toggleList[i].txt0 at:&j];
			if( theCell==nil )
				theCell = [self findCellWithTitle:toggleList[i].txt1 at:&j];
			if( theCell==nil ) break;

			if( fnt ) [theCell setTitleFont:fnt];
			[entryForm getCellFrame:&cellFrame at:j :0];
			tglButton = toggleList[i].tglButton;
			toggleList[i].formInx = j;
			
			cellFrame.W = [theCell titleWidth];
			[tglButton setFrame:&cellFrame];
			[entryForm addSubview:tglButton];
			[tglButton display];	//	<---
			toggleList[i].isInWindow = YES;
		}
		i++;
	}
	return self;
}


- setToggleButtonsFor:(int)eType
{
	[self perform:@selector(setDelayedToggleButtonsFor:)
		with:(id)eType
		afterDelay:0 cancelPrevious:YES];

	//[self setDelayedToggleButtonsFor:eType];
	return self;
}


static	char itemTextBuffer[1024];	// variable length ??????

- (const char *)titleOf:(configListType *)item
{
	char *pnt = itemTextBuffer;
	int	mode = item->mode[entryType];

	strcpy( itemTextBuffer, item->fieldName );
	while( *pnt ) pnt++;
	*pnt++ = ' ';
	if( (3 IN mode )==REQUIRED ) *pnt++ = '·';
	else *pnt++ = 0x80;
	*pnt = '\0';

	return itemTextBuffer;
}


- (BOOL)fillFormAt:(int)inx for:(locType)location
{
	// copy into a formcell
	//	and test for linefeed

	int		i;
	char	c, *theString;
	
	if( location.length==0 ){
		[entryForm setStringValue:NULL at:inx];
		return NO;
	}
	[viewBuffer setMinSizeTo:location.length];
	[theObject convertRawField:location to:[viewBuffer buffer]];

	[entryForm setStringValue:[viewBuffer buffer] at:inx];
	
	i = 0;
	theString = [viewBuffer buffer];
	while( c=theString[i++] ){
		if( c=='\n' ) return YES;
	}
	return NO;
}


- toggle:sender
{
	toggleListType *toggleList = [preferences toggleList];
	int	tag = [sender tag];
	int	fieldInx;
	int	formInx = toggleList[tag].formInx;
	configListType	*item;
	locType	location;

	if( toggleList[tag].state==0 ){
		fieldInx = [preferences indexOfFieldname:toggleList[tag].txt1];
		toggleList[tag].state = 1;
	}
	else{
		fieldInx = [preferences indexOfFieldname:toggleList[tag].txt0];
		toggleList[tag].state = 0;
	}

	item = ((configListType*)[configList elementAt:fieldInx]);
	[entryForm setTitle:[self titleOf:item] at:formInx];
	location.start = item->start;	// set by the parser
	location.length = item->length;
	[self fillFormAt:formInx for:location];
	
	return self;
}



//	a key is a string of characters.
//	all characters are allowed exept the comma
//	BUT: if the entryType is STRING the following 10 characters are not allowed
//	" # % ' ( ) , { }

#define isOneOfTheTen(x)	(x=='"' || x=='#' || x=='%' || x=='\''\
	 || x=='(' || x==')' || x==',' || x=='=' || x=='{' || x=='}')


- (BOOL) key:(const char *)key isValidFrom:(int*)first to:(int*)last
{
	unsigned char c;
	int	i = 0;

	if( (entryType==PREAMBLE) || (entryType==COMMENT) ) return YES;
	if( key==NULL ) return NO;
	if( (c = key[i])=='\0' ) return NO;
	while( NXIsSpace(c) ) c = key[++i];
	*first = i;
	while( c ){
			//if( isOneOfTheTen(c) || (c>127) ){	// not valid
			if( c==',' ||
				( (entryType==STRING) && isOneOfTheTen(c) ) ){	// not valid
				*last = i;	// here is the error
				return NO;
			}
			if( NXIsSpace(c) ) break; 
			c = key[++i];
	}
	*last = i;
	if( c=='\0' ) return YES;

	// skip trailing blanks
	while( NXIsSpace(c) ) c = key[++i];
	if( c ) return NO;
	return YES;
}




- enableReplaceButton
{
	char	buttonBuffer[128], *lastDot;

	//if( [replaceButton isEnabled] ) return self;

	sprintf( buttonBuffer, "update ." );
	lastDot = rindex( buttonBuffer, '.' );
	[theObject copyRange:[theObject key] toBuffer:lastDot];
	[replaceButton setTitle:buttonBuffer];
	[replaceButton setEnabled:YES];
	return self;
}


- enableAddButton
{
	char	buttonBuffer[128], *lastDot;

	if( [addButton isEnabled] ) return self;

	sprintf( buttonBuffer, "add to file %s", [theFile fileName] );
	lastDot = rindex( buttonBuffer, '.' );
	*lastDot = '\0';
	[addButton setTitle:buttonBuffer];
	[addButton setEnabled:YES];
	return self;
}


- (void) updateButtons
{
	[clearButton setEnabled:YES];
	if( theFile==nil ){	// no file selected
		[revertButton setEnabled:NO];
		[addButton setEnabled:NO];
		[replaceButton setEnabled:NO];
		return;
	}

	// file exists
	if( theObject==nil ){
		// a file but no object  (maybe we create a new object)
		[revertButton setEnabled:NO];
		[replaceButton setEnabled:NO];
		
		if( isDirty &&( (entryType==PREAMBLE)||(entryType==COMMENT) ) ){
			[self enableAddButton];
			return;
		}

		if( isDirty
			&& keyIsValid
			&& (objWithThisKey==nil) )
		[self enableAddButton];	// key is unique

		else [addButton setEnabled:NO];
		return;
	}

	// both file and object exist
	if( (isDirty==NO) && (objWithThisKey==theObject) ){
		[revertButton setEnabled:NO];
		[replaceButton setEnabled:NO];
		[addButton setEnabled:NO];
		return;
	}

	// both file and object exist and the text has been edited
	[revertButton setEnabled:YES];
	if( keyIsValid ){
		if( objWithThisKey==nil ){	// key is valid and unique
			[self enableReplaceButton];
			[self enableAddButton];
			return;
		}

		// key is valid but not unique
		if( objWithThisKey==theObject ){	// it's me
			[self enableReplaceButton];
			[addButton setEnabled:NO];
			return;
		}
		// key is valid but belongs to another entry
		[replaceButton setEnabled:NO];
		[addButton setEnabled:NO];
		return;
	}
	
	// key is syntactically not valid
	[replaceButton setEnabled:NO];
	[addButton setEnabled:NO];
	return;
}


- raw:(const char *)rawText toCooked:(char *)cookedText
{
	return self;
}


- (const char *)convertText:(int)i
{
	const char* txt = [entryForm stringValueAt:i];
	if( cooked==NO ) return txt;

	if( (txt[0]=='"') || (txt[0]==LBRACE) ){
		[cookedBuffer setText:txt];
		return [cookedBuffer buffer]+1;
	}
	return txt;
}


- (void) clearConfigList
{
	int	i;
	configListType	*item;

	// clear location entries in configList 
	for( i=0; i<[configList count]; i++ ){
		item = ((configListType*)[configList elementAt:i]);
		item->start = 0;
		item->length = 0;
	}

}


// called from controller (newButton)
- newEntryFor:(BibliographicFile *)file
{
	[self setEntry:nil inFile:file];
	NXAttachPopUpList( popUpButton, popUpListNew );
	[self drawTemplate:-1];
	//[self clear:self];
	[inspector makeKeyAndOrderFront:self];
	if( [keyField isEnabled] ) [keyForm selectTextAt:0];
	else [noteView setSel:0 :0];
	
	[self clearConfigList];

	return self;
}


- fillCharMatrix:sender
{
	unsigned char str[8];
	char **tbl = [BibTexParser NX2TeX];
	//char *texString;
	int	i;

	str[1] = '\0';
	[[charMatrix window] disableDisplay];
	for( i=0; i<128; i++ ){
		id	cell = [charMatrix cellAt:i/8 :i%8];
		str[0] = i + 0x80;
		[cell setTitle:str];
		//texString = tbl[ i ];
		if( tbl[i][1]=='?' ) [cell setEnabled:NO];
	}
	[[[charMatrix window] reenableDisplay] display];
	[[charMatrix window]setBecomeKeyOnlyIfNeeded:YES];
	return self;
}


- firstCall
{
	int	index;
	NXRect	entryFrame;
	
	[NXApp loadNibSection:"EntryInspector.nib" owner:self];

	if( BIBTEST==NO ) [[bufferedButton removeFromSuperview] free];
	
	[inspector useOptimizedDrawing:YES];

	[entryForm setInterline:0];	// ******

	preferences = [Preferences new];	
	STRING = [preferences indexOfEntryname:"STRING"];	
	PREAMBLE = [preferences indexOfEntryname:"PREAMBLE"];	
	COMMENT = [preferences indexOfEntryname:"COMMENT"];
	entryNameList = [preferences entryNameList];
	configList = [preferences configList];	
	[self clearConfigList];
	popUpList = [[PopUpList alloc] init];
	popUpListNew = [[PopUpList alloc] init];

	for( index=0; index<[entryNameList count]; index++ ){
		const char *title = (const char *)[entryNameList objectAt:index];
		[popUpList addItem:title];
		[popUpListNew addItem:title];
	}

	// clean the popUpList
	[[popUpList removeItem:"STRING"] free];
	[[popUpList removeItem:"PREAMBLE"] free];
	[[popUpList removeItem:"COMMENT"] free];

	// size both lists to fit, setTarget
	[popUpList sizeToFit];	
	[[popUpList setAction:@selector(bibPopup:)] setTarget:self];
	[popUpListNew sizeToFit];	
	[[popUpListNew setAction:@selector(bibPopup:)] setTarget:self];

	[popUpButton setFont:[Font newFont:"Courier" size:12.0 ]];
	
	popUpListString = [[PopUpList alloc] init];
	[popUpListString addItem:"STRING"];	
	popUpListPreamble = [[PopUpList alloc] init];
	[popUpListPreamble addItem:"PREAMBLE"];	
	popUpListComment = [[PopUpList alloc] init];
	[popUpListComment addItem:"COMMENT"];

	// we start with the "normal" list
	NXAttachPopUpList( popUpButton, popUpList );
	[popUpList sizeButton:popUpButton];	

	// get the original windowFrame	(without entryForm and scrollView)
	[entryForm getFrame:&entryFrame];	
	[inspector getFrame:&windowFrame];
	[noteView getFrame:&minTextFrame];	// as set in IB
	windowFrame.Y += windowFrame.H; // northwest_corner
	windowFrame.H -= entryFrame.H; // without entryForm
	windowFrame.H -= minTextFrame.H; // without scrollView
	// windowFrame is now the raw frame (only height is of interest)
	// without entryform
	
	entryFormMask = [entryForm autosizing];		// as made in IB
	scrollViewMask = [scrollView autosizing];	// as made in IB
	scrollViewMask = [marker autosizing];		// as made in IB
	[noteView setDelegate:self];
	[noteView setTag:120];
	[noteView setFont:[Font userFontOfSize:0.0 matrix:NX_FLIPPEDMATRIX]];
	theFont = [[entryForm cellAt:0 :0] titleFont];

	isDirty = NO;
	textBufferList = [[List alloc] init];
	[marker renewRows:0 cols:1];
	[marker setFont:[Font newFont:"Symbol" size:12.0 ]];
	[self drawTemplate:-1];
	[inspector orderFront:self];
	
	[self fillCharMatrix:self];
	[self initButtons];
	[fieldEditor setMonoFont:YES];
	[fieldEditor setCharFilter:NXFieldFilter];
	cooked = YES;
	return self;
}


- (void) clearBuffers
{
	int	i;

	for( i=[textBufferList count]-1; i>=0; i-- )
		[[textBufferList objectAt:i] clear];
}


- prefsDidChange
{
	prefsDidChange = YES;
	if( [inspector isDisplayEnabled]==NO ) return self;
	[self drawTemplate:-1];
	[self revert:self];
	return self;
}


- (void) sizeBufferListTo:(int)index
{
	while( [textBufferList count]<=index )
		[textBufferList addObject:[[BufferClass alloc] init] ];
}


- (char *) listBufferFor:(int)inx
{
	if( [textBufferList count] <= inx ){
		[self sizeBufferListTo:inx];
	}
	return [[textBufferList objectAt:inx] buffer];
}


- (char *) listBufferFor:(int)inx size:(int)size
{
	if( [textBufferList count] <= inx ){
		[self sizeBufferListTo:inx];
	}

	[[textBufferList objectAt:inx] setMinSizeTo:size];
	return [[textBufferList objectAt:inx] buffer];
}


- noteSelect:sender	// sender is Matrix
{
	int	row = [sender selectedRow];
	char *aTextBuffer;	// source buffer

	if( row<0 ) return self;

	aTextBuffer = [self listBufferFor:row];
	if( noteIsDirty ){	// write the text back to the buffer
		char *actualTextBuffer =
			[self listBufferFor:actualRow size:[noteView textLength]+1];
		[noteView getSubstring:actualTextBuffer
			start:0 length:[noteView textLength]+1];
	}
	[noteView setSel:0 :[noteView textLength]];
	[noteView replaceSel:aTextBuffer];
	noteIsDirty = NO;
	actualRow = row;
	return self;
}


- copyLoc:(locType)loc ofObject:obj to:(int)inx
{
	char *aTextBuffer = [self listBufferFor:inx size:loc.length];

	if( entryType==COMMENT ){
		[obj copyRange:loc toBuffer:aTextBuffer];
	}
	else [obj convertRawField:loc to:aTextBuffer];
	return self;
}


//	the key-field is already filled

- showString:(int)k
{
	configListType	*item;
	locType	location;
	int	NOTE = [preferences indexOfFieldname:"NOTE"];

	//	STRING has no fieldname, it's content is stored in NOTE
	//	PREAMBLE has no fieldname, it's content is stored in NOTE
	//	COMMENT has no fieldname, it's content is stored in NOTE
	item = ((configListType*)[configList elementAt:NOTE]);
	location.start = item->start;	// set by the parser
	location.length = item->length;
	
	// copy the content to a buffer at index [0];
	[self copyLoc:location ofObject:theObject to:0];

	[noteView setSel:0 :[noteView textLength]];
	[noteView replaceSel:[self listBufferFor:0]];
	[[entryForm renewRows:0 cols:1] sizeToCells];
	[noteMatrix renewRows:0 cols:0];
	return self;
}


- resizeInspector:(NXRect *)fr
{
	NXRect	entryFrame, textFrame, minFrame;

	[entryForm getFrame:&entryFrame];
	[scrollView getFrame:&textFrame];

	newWindowFrame = windowFrame;
	newWindowFrame.H =	// new minimal frame
			windowFrame.H
			+ entryFrame.H
			+ minTextFrame.H;
	minFrame = newWindowFrame;
	minFrame.W = 300.0;
	[inspector setMinSize:&minFrame.size];
	newWindowFrame.H =	// new real frame
			windowFrame.H
			+ entryFrame.H
			+ textFrame.H;

	// northwest_corner --> southeast_corner
	newWindowFrame.Y -= newWindowFrame.H;
	*fr = newWindowFrame;
	return self;
}



// 	skips leading blanks
//	returns a pointer to a string from the first nonblanl
//	character. The string ends before the first blank
//	behind the string.
//	used in printing key, digits and the title of the fields
- (const char *)stringOf:(const char *)txt
{
	int	i = 0;
	char c;

	while( (c=*txt++)==' ' );	// skip leading blanks
	while( c && (c!=' ') ){itemTextBuffer[i++] = c; c = *txt++;}
	itemTextBuffer[i] = '\0';
	return itemTextBuffer;
}


- (void) addURLat:(int)inx
{
	NXRect	cellFrame;
	Cell	*theCell = [entryForm cellAt:inx :0];
	Cell	*markerCell = [marker cellAt:inx :0];

	[entryForm getCellFrame:&cellFrame at:inx :0];
	fprintf( stderr, "add type=%d\n", [markerCell type] );
	//[markerCell setIcon:"Images/smallIcon"];
	[theCell setIcon:"Images/smallIcon"];
	fprintf( stderr, "add type=%d %s\n",
		[theCell type], [theCell icon] );
}


- (void) addMarker:(BOOL)m at:(int)inx
{
	id	cell;
	int	row, col;
	[marker addRow];
	[marker getNumRows:&row numCols:&col];
	cell = [marker cellAt:(row-1) :0];
	if( m ){
		[cell setStringValue:"\272"];
		//[self addURLat:inx];
	}
	else [cell setStringValue:NULL];
}


- (void) setMarker:(BOOL)m at:(int)inx
{
	id	cell = [marker cellAt:inx :0];
	const char *str = [cell stringValue];

	if( m == (str!=NULL) ) return;

	if( m ){
		[cell setStringValue:"\272"];
		//[self addURLat:inx];
	}
	else [cell setStringValue:NULL];
}


- (void) drawTemplate:(int)aType	// empty inspector
{
	int	i, count, mode, rows, cols;
	configListType	*item;
	NXRect	winFrame;
	

	if( aType<0 ){
		entryType = [preferences indexOfEntryname:(char *)[popUpButton title]];
	}
	else entryType = aType;

	[inspector disableDisplay];
	[inspector endEditingFor:nil];
	
	[keyForm setNextText:entryForm];
	[keyField setEnabled:YES];
	[keyForm selectTextAt:0];

	[[entryForm renewRows:0 cols:1] sizeToCells];	// clear the matrix
	[marker renewRows:0 cols:1];

	if( 	(entryType==STRING)
		||	(entryType==PREAMBLE)
		||	(entryType==COMMENT) ){ // special handling

		[[noteMatrix renewRows:0 cols:1] sizeToCells];
		if( entryType==PREAMBLE )[keyField setEnabled:NO];
		if( entryType==COMMENT )[keyField setEnabled:NO];
		if( entryType==STRING ) [keyForm setNextText:noteView];
		[self resizeInspector:&winFrame];
		[inspector reenableDisplay];
		[inspector placeWindowAndDisplay:&winFrame];
		return;
	}

	[noteMatrix renewRows:0 cols:1];

	// set the empty forms
	for( i=0, count=0; i<[configList count]; i++ ){
		item = ((configListType*)[configList elementAt:i]);
		item->tag = -1;	// clear
		mode = item->mode[entryType];
		if( SHOW IN mode ){
			if( (LARGE IN mode) ){
				id	cell;
				int	row, col;
				[noteMatrix addRow];
				[noteMatrix getNumRows:&row numCols:&col];
				cell = [noteMatrix cellAt:(row-1) :0];
				[cell setTitle:item->fieldName];
			}
			else{	// normal form
				[entryForm addRow];
				[[entryForm cellAt:count :0] setTitleFont:theFont];
				item->tag = count;
				[entryForm setTitle:[self titleOf:item] at:count];
				count++;
			}
		}
	}

	[noteMatrix sizeToCells];
	[entryForm sizeToCells];
	[entryForm getNumRows:&rows numCols:&cols];
	[marker renewRows:rows cols:cols];
	[marker sizeToCells];
	for( i=0; i<rows; i++ ){
		[[marker cellAt:i :0] setStringValue:NULL];
	}


	[self resizeInspector:&winFrame];
	[self setToggleButtonsFor:entryType];

	[self clearBuffers];
	prefsDidChange = NO;
	[inspector reenableDisplay];
	[inspector placeWindowAndDisplay:&winFrame];
}


- (void) fillTemplate
{
	int	i, j, n, mode;
	configListType	*item;
	locType	location, range;
	BOOL multiLine = NO;

[self showAllFieldsFor:entryType];
[[inspector reenableDisplay] displayIfNeeded];
return;

	[theObject setKeyTo:keyField];
	[marker renewRows:0 cols:1];

	if( (entryType==STRING)||(entryType==PREAMBLE)||(entryType==COMMENT) ){
		[self showString:entryType];
		[[inspector reenableDisplay] displayIfNeeded];
		[scrollView display];
		return;
	}
	
	for( i=0, j=0, n=0; i<[configList count]; i++ ){
		item = ((configListType*)[configList elementAt:i]);
		mode = item->mode[entryType];
		location.start = item->start;	// set by the parser
		location.length = item->length;
		[viewBuffer setMinSizeTo:location.length];
		if( SHOW IN mode ){
			if( (LARGE IN mode) ){
				[self copyLoc:location ofObject:theObject to:n];
				n++;
				continue;
			}
			multiLine = [self fillFormAt:j for:location];
			[self addMarker:multiLine at:j];
			j++;
		}
	}
	noteIsDirty = NO;
	range = [theObject range];
	sprintf( [viewBuffer buffer], "line %d, charpos %d, length %d",
		[theObject line], range.start, range.length );
	[positionField setStringValue:[viewBuffer buffer]];

	[self noteSelect:noteMatrix];
	[marker sizeToCells];
	[[inspector reenableDisplay] displayIfNeeded];
	[scrollView display];
	[marker display];
}


- (void) showAllFieldsFor:(int)aType
{
	int	i, j, count, mode;
	configListType	*item;
	locType	location;
	BOOL	multiLine;
	BOOL	mustEnable = NO;

	if( (aType==STRING)||(aType==PREAMBLE)||(aType==COMMENT) ){
		[self showString:aType];
		return;
	}

	if( [inspector isDisplayEnabled] ){
		[inspector disableDisplay];
		mustEnable = YES;
	}
	// how many fields are needed ?

	configList = [preferences configList];	// actual list
	for( i=0, count=0; i<[configList count]; i++ ){
		item = ((configListType*)[configList elementAt:i]);
		mode = item->mode[aType];
		item->tag = -1;	// clear
		if( LARGE IN mode ) continue;
		if( SHOW IN mode ) count++;
	}

	[[entryForm renewRows:count cols:1] sizeToCells];
	[noteMatrix renewRows:0 cols:1];

	[marker renewRows:0 cols:1];
	for( i=0, j=0; i<[configList count]; i++ ){
		item = ((configListType*)[configList elementAt:i]);
		mode = item->mode[aType];
		location.start = item->start;	// set by the parser
		location.length = item->length;
		if( SHOW IN mode ){

			if( (LARGE IN mode) ){	// copy to the noteView - textObject
				id	cell;
				int	row, col;
				[noteMatrix addRow];
				[noteMatrix getNumRows:&row numCols:&col];
				cell = [noteMatrix cellAt:(row-1) :0];
				[cell setTitle:item->fieldName];
				// copy the content to textObject[row-1];
				[self copyLoc:location ofObject:theObject to:row-1];
			}
			else{	// copy into a formcell
				[[entryForm cellAt:j :0] setTitleFont:theFont];
				[entryForm setTitle:[self titleOf:item] at:j];
				item->tag = j;
				multiLine = [self fillFormAt:j for:location];
				[self addMarker:multiLine at:j];
				j++;
			}
		}
	}
	[noteMatrix sizeToCells];
	[marker sizeToCells];
	noteIsDirty = NO;
	[self noteSelect:[noteMatrix selectCellAt:actualRow :0]];
	if( mustEnable ) [inspector reenableDisplay];
}


// here is a method envoked from controller and finder

- setEntry:anObject inFile:(BibliographicFile *)file
{
	locType	range;

	if( inspector==nil ){
		if( file ) [self firstCall];	// else no inspector needed
		else return self;
	}

	if(0)if( isDirty==YES ){	// doesn't work
		int rtn;
		//fprintf( stderr, "isdirty\n" );
		rtn = NXRunAlertPanel("EntryInspector", 	// title
				"entry did change",		// message
				"  save  ",				// 1: default button
				"  don't save  ",		// 0: alternate "
				"  cancel  " 			//-1: other     "
			  );
		if( rtn==NX_ALERTDEFAULT ){
			//[self entryChangedIsNew:isNew];
			//[self makeEntry:self];
		}
		if( rtn==NX_ALERTOTHER ) return self;

	}

	keyIsValid = (anObject==nil) ? NO : YES;
	objWithThisKey = anObject;

	if( (theObject==anObject) && (theFile==file) ){
		if( isDirty==NO ) return self;
		isDirty=NO;	// restore
	}

	theObject = anObject;
	theFile = file;

	[entryForm setAutosizing:entryFormMask];
	[scrollView setAutosizing:scrollViewMask];

	if( (theFile == nil) || (theObject == nil) ){
		[self clear:self];
		return self;
	}

	[inspector disableDisplay];
	[inspector endEditingFor:nil];
	[self enableAddButton];	// set the filename as title
	[self enableReplaceButton];	// sets key as title
	[self updateButtons];

	// theObject and theFile are both existent
	[inspector setTitleAsFilename:[theFile fullPath]];

	if( NO /*isDirty==YES*/ ){
		//entryType = aType;
	}
	else {

		[theObject parseSelf];

		[leftBraceButton setState:( [theObject delimiter]==RBRACE )?0:1];
		[rightBraceButton setState:( [theObject delimiter]==RBRACE )?0:1];

		if(0)if( (entryType==[theObject entryType]) && (prefsDidChange==NO) ){
			// the new entry is the same entrytype as the former entry
			// we need no resizing, no new toggle buttons
			[self fillTemplate];
			[inspector orderFront:self];
			return self;
		}
		prefsDidChange=NO;
		entryType=[theObject entryType];

		[keyField setEnabled:YES];
		if( entryType==STRING ){ 
			NXAttachPopUpList( popUpButton, popUpListString );
			[keyForm setNextText:noteView];
		}
		else 
		if( entryType==PREAMBLE ){
			NXAttachPopUpList( popUpButton, popUpListPreamble );
			[keyField setEnabled:NO];
		}
		else
		if( entryType==COMMENT ){
			NXAttachPopUpList( popUpButton, popUpListComment );
			[keyField setEnabled:NO];
		}
		else{
			NXAttachPopUpList( popUpButton, popUpList );
			[keyForm setNextText:entryForm];
		}

		isDirty = NO;
		isNew = NO;
	}

	[theObject setKeyTo:keyField];

	range = [theObject range];
	sprintf( [viewBuffer buffer], "line %d, charpos %d, length %d",
		[theObject line], range.start, range.length );
	[positionField setStringValue:[viewBuffer buffer]];
	
	[self showAllFieldsFor:entryType];
	
	// and now resize the window
	[self resizeInspector:&newWindowFrame];
	[self setToggleButtonsFor:entryType];


	[popUpButton setTitle:
		(const char *)[entryNameList objectAt:entryType]];

	[inspector reenableDisplay];

	[self displayEntry:self];
	
	return self;
	
}


- displayEntry:sender
{
	[inspector placeWindowAndDisplay:&newWindowFrame];
	[inspector orderFront:self];
	return self;
}


//	erases the form, nothing else is changed

- clear:sender
{
	int	rows, cols, i;
	
	if( inspector==nil ) return self;

	[inspector disableDisplay];
	[inspector endEditingFor:nil];

	if( theFile ){
		[inspector setTitleAsFilename:[theFile fullPath]];
		//[self callSomethingNotImplemented];	// Make me crash!!!
	}
	else{
		[inspector setTitle:"Entry Panel"];
	}

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

	for( i=0; i<rows; i++ ){
		[entryForm setStringValue:NULL at:i];
		[[marker cellAt:i :0] setStringValue:NULL];
	}
	[keyField setStringValue:NULL];
	//[noteView setText:""];	// loescht nicht
	isDirty = (theObject)? YES :NO;	// --> revert is possible
	keyIsValid = NO;
	[self updateButtons];
	[clearButton setEnabled:NO];
	
	[self clearConfigList];	// !!!!
	[[inspector reenableDisplay] displayIfNeeded];
	[marker display];
	[noteView setText:""];
	[scrollView displayIfNeeded];
	return self;
}


- bibPopup:sender	// sender is Matrix
{
	int	originalEntryType = [theObject entryType];

	entryType = [sender selectedRow];

	if( theObject==nil ){
		[self drawTemplate:entryType];
		return self;
	}
	entryType = [sender selectedRow];
	if( ((originalEntryType==STRING) && (entryType!=STRING)) ||
		((originalEntryType!=STRING) && (entryType==STRING)) ){
		NXRunAlertPanel("EntryInspector", 	// title
				"you cannot change %s to %s",		// message
				"  OK  ",	// 1: default button
				NULL,		// 0: alternate "
				NULL, 		//-1: other     "
				(const char *)[entryNameList objectAt:originalEntryType],
				(const char *)[entryNameList objectAt:entryType]
				);
		[sender selectCellAt:originalEntryType :0];
		return self;

		
	}
	[inspector endEditingFor:nil];
	isDirty = isDirty || (entryType!=[theObject entryType]);
	[self drawTemplate:entryType];	// empty inspector
	[self fillTemplate];
	[self updateButtons];
	return self;
}


- (BOOL)saveDigitsFrom:(int)inx :(const char *)txt
{
	char *digitText;
	int	count = 0;

	while( NXIsSpace(*txt) ) txt++;
	if( *txt=='\0' ) return NO;
	digitText = (char *)txt;	// first digit
	while( NXIsDigit(*txt) ){ txt++; count++; }
	while( NXIsSpace(*txt) ) txt++;	// trailing blanks
	if( *txt ) return NO;

	NXPrintf( localStream, ",\n\t%s\t= ",
		[self stringOf:[entryForm titleAt:inx]] );
	while( count-- ){ NXPutc( localStream, *digitText ); digitText++; }
	
	return YES;
}


- convert:(const char *)txt toStream:(NXStream *)toStream
{
	unsigned char c;
	int	i = 0;
	int	level = 0;
	BOOL leftPartIsEmpty = YES;

	// skip over blanks
	c = txt[0];
	while( NXIsSpace(c) ) c = txt[++i];

	if( c=='\0' ) return self;	// empty text

	while( YES ){
		if( c=='\0' ) return self;
		if( c!='@' ){
			if( leftPartIsEmpty==NO ) ADD_HASH_MARK;
			NXPutc(toStream, LBRACE);
			while( c && ( (c!='@')||(level>0) ) ){
				if( c>=0x80 ){
					unsigned char tc;
					const char *texText = [BibTexParser NX2TeX][ c-0x80 ];
					while( tc = *texText++ ) NXPutc(toStream, tc);
				}
				else NXPutc(toStream, c);
				if( c==LBRACE ) level++;
				if( c==RBRACE ) level--;
				c = txt[++i];
			}
			NXPutc(toStream, RBRACE);
			if( c=='\0' ) return self;
			leftPartIsEmpty = NO;
		}

		// c == '@'
		if( leftPartIsEmpty==NO ) ADD_HASH_MARK;
		c = txt[++i];
		//while( NXIsAlpha(c) )	// STRING allows more characters --> theTen
		while( c && (c!=',') && !NXIsSpace(c) && (c!='@') ){
			NXPutc(toStream, c);
			c = txt[++i];
			leftPartIsEmpty = NO;
		}
		while( NXIsSpace(c) ) c = txt[++i];	// skip trailing blanks
	}	
	return self;
}


- parseKey
{
	int		first, last,i=0;
	const	char	*key;
	char	keyBuffer[64];

	if( (entryType==PREAMBLE)||(entryType==COMMENT) ){
		keyIsValid = YES;
		objWithThisKey=theObject;
	return self;
	}

	keyIsValid = [self key:[keyField stringValue] isValidFrom:&first to:&last];

	if( keyIsValid==NO ){
		objWithThisKey = nil;
		return self;
	}
	key = [keyField stringValue];
	while( *key ){ keyBuffer[i++] = *key++; }	// copy the key
	keyBuffer[last] = '\0';

	objWithThisKey = [theFile findKey:&keyBuffer[first]];
	return self;
}


- entryChangedIsNew:(BOOL)n
{
	isNew = n;
	isDirty = NO;
	[self makeEntry:self];
	[theFile setDirty];
	[self parseKey];
	[self updateButtons];
	return self;
}


- addNewEntry:sender
{
	return [self entryChangedIsNew:YES];
}


- replaceEntry:sender
{
	return [self entryChangedIsNew:NO];
}


- (BOOL) textIsEmpty:(const char *)txt
{
	int	i = 0;
	unsigned char	c = txt[0];
	while( NXIsSpace(c) ) c = txt[++i];

	return c=='\0';
}


- makeEntry:sender	// from add-/replace button
{
	int			rows, cols, i, pos;
	char		*outString;
	NXStream	*theStream;

	if(0)if( theObject==nil ){	// it's really new
		theStream = [theFile stream];
		return self;
	}
	
	if ( localStream==NULL ){
		localStream = NXOpenMemory( NULL, 0, NX_READWRITE );
	}
	NXSeek(localStream, 0, NX_FROMSTART);
	[entryForm getNumRows:&rows numCols:&cols];

	// update changes (if any)
	if( noteIsDirty ){	// write the text back to the buffer
		char *actualTextBuffer =
			[self listBufferFor:actualRow
						size:[noteView textLength]+1];
		[noteView getSubstring:actualTextBuffer
			start:0 length:[noteView textLength]+1];
	}

	//	print all fields from the view
	
	if( entryType==PREAMBLE ){	// PREAMBLE has no keyfield
		THE_TYPE;	// @PREAMBLE
		BIG_LEFT;
			//LEFTQUOTE;	ACTUALBUFFER;	RIGHTQUOTE;
			ACTUALBUFFER;
		BIG_RIGHT;
	}
	else	
	if( entryType==COMMENT ){	// COMMENT has no keyfield
		THE_TYPE;	// @COMMENT
		BIG_LEFT;
			ACTUALBUFFER;
		BIG_RIGHT;
	}
	else	
	if( entryType==STRING ){
		THE_TYPE;	// @STRING
		BIG_LEFT;
			//THE_KEY; ASSIGN; LEFTQUOTE; ACTUALBUFFER;	RIGHTQUOTE;
			THE_KEY; ASSIGN; ACTUALBUFFER;
		BIG_RIGHT;
	}
	else{
		THE_TYPE;	// @BOOK or so 
		BIG_LEFT;
		THE_KEY;	// key value

		for( i=0; i<[configList count]; i++ ){
			locType	location;
			configListType	*item =
					((configListType*)[configList elementAt:i]);
			int	tag = item->tag;
			int	mode = item->mode[entryType];
			//fprintf( stderr, "%3d  %s\n", item->tag, item->fieldName );

			if( (tag>=0)
				&& [entryForm stringValueAt:tag]
				&& (![self textIsEmpty:[entryForm stringValueAt:tag]]) ){
	
					outString = (char *)[self convertText:tag];
	
					// check for digits
					if( [self saveDigitsFrom:tag :outString] ) continue;
	
					NEXTENTRY([self stringOf:[entryForm titleAt:tag]]);
					//LEFTQUOTE;
						[self convert:[entryForm stringValueAt:tag]
							toStream:localStream];
					//RIGHTQUOTE;
			}
			else if( (tag<0) && !(LARGE IN mode) ) {
				location.start = item->start;	// set by the parser
				location.length = item->length;
				if( location.length > 0 ){
					NXPrintf( localStream, ",\n\t%s\t= ", item->fieldName );
					[theObject copyRange:location toStream:localStream];
				}

			}
		}
		// copy LARGE entry fields
		{
			int	row, col, i;
			[noteMatrix getNumRows:&row numCols:&col];
			for( i=0; i<row; i++ ){
				id	cell = [noteMatrix cellAt:i :0];
				char *buffer = [self listBufferFor:i];

				if( buffer && (![self textIsEmpty:buffer]) ){
					NEXTENTRY([cell title]);
					//LEFTQUOTE;
					[self convert:(const char *)buffer toStream:localStream];
					//RIGHTQUOTE;
				}
			}
		}
		NEWLINE;
		BIG_RIGHT;
	}

	NXFlush( localStream );
	pos = NXTell( localStream );
	// we need the buffer of this stream for NXWrite
	NXGetMemoryBuffer( localStream,
		&localBuffer, &locBufferLength, &locBufferMaxLength);

	// we have now a bibtex - conforming text of the entryObject
	// append the text	
	if( isNew==YES ){
		theObject = [theFile appendNewText:pos :localBuffer];
		[theObject parseSelf];	// @@@@@ 1.1
	}
	else{
		//[theFile replace:pos :localBuffer forObject:theObject];
		[theFile replaceTextOf:theObject with:localBuffer length:pos];
	}

	[[NXApp delegate] update:theObject for:theFile isNew:isNew shiftLit:NO];

	return self;
}


- revert:sender
{
	isDirty=YES;
	[self setEntry:theObject inFile:theFile];
	//isDirty=NO;
	[self updateButtons];
	return self;
}


// copy selected text to findPasteBoard

- enterSelection:sender
{
	[[NXApp delegate] enterSelectionFor:[inspector firstResponder]];
	return self;
}



- mainBrace:sender
{
	[leftBraceButton setState:[sender state]];
	[rightBraceButton setState:[sender state]];
	if( theFile ){
		isDirty = YES;
		[self updateButtons];
	}

	return self;
}


- showCharPanel:sender
{
	[[charMatrix window] orderFront:self];
	return self;
}


- insertText:(char *)txt
{
	id	keyWindow	= [NXApp keyWindow];
	id	delegate	= [keyWindow delegate];
	id	editor		= [keyWindow firstResponder];
	NXSelPt start, end;

	if( ![editor respondsTo:@selector(getSel::)] ) return self;

	[editor getSel:&start :&end];
	[editor replaceSel:txt];
	if( [delegate respondsTo:@selector(textDidGetKeys:isEmpty:)] ){
		[delegate textDidGetKeys:editor isEmpty:NO];
	}

	return self;
}


extern	fnct_convertRawString();


- insertMacro:(char *)txt isRaw:(BOOL)isRaw
{
	id	editor = [inspector firstResponder];
	NXSelPt start, end;

	if( ![editor isKindOf:[Text class]] ){
		return self;
	}
	else
	[editor getSel:&start :&end];
	if( start.cp<0 ){	// nothing selected (noteView?)
		return self;
		//[editor getSel:&start :&end];
	}
	if( isRaw ){
		[viewBuffer setMinSizeTo:strlen(txt)+1];
		fnct_convertRawString(txt, [viewBuffer buffer] );
		[editor replaceSel:[viewBuffer buffer]];
	}
	else [editor replaceSel:txt];
	//[editor showCaret];
	[self textDidGetKeys:editor isEmpty:NO];
	return self;
}


- specialMatrix:sender
{
	[self insertText:(char *)[[sender selectedCell] title]];
	return self;
}


//	* * * * * * *	delegate methods

- windowDidResignKey:sender
{
	return self;
}


- windowWillReturnFieldEditor:sender toObject:client
{
	return nil;

	// we need something like 'endEditingfor:'
	
	fprintf( stderr, "client is %s", [client name] );
	if( [client class]==[Button class] ){
		fprintf( stderr, "  %s", [client title] );
	}
	if( [client class]==[TextField class] ){
		fprintf( stderr, "  %d", [client tag] );
	}

	fprintf( stderr, "\n" ); 
	if( [client class]==[Form class] ){
		if(0)[sender perform:@selector(endEditingFor:)
			with:(id)nil afterDelay:0 cancelPrevious:YES];
	if(1)
	fprintf( stderr, "sender is %s firstResponder = %s index=%d tag=%d\n",
		[sender name],
		[[sender firstResponder] name],
		[client selectedIndex],
		[client tag] );
		[sender endEditingFor:nil];
		return fieldEditor;
	}

	return nil;
}


- windowWillResize:sender toSize:(NXSize *)frameSize
{
	[entryForm  setAutosizing: NX_WIDTHSIZABLE | NX_MINYMARGINSIZABLE];
	[marker		setAutosizing: NX_MINXMARGINSIZABLE | NX_MINYMARGINSIZABLE];
	[scrollView setAutosizing: NX_WIDTHSIZABLE | NX_HEIGHTSIZABLE ];
	return self;
}


- windowDidResize:sender
{
	NXRect	newFrame;
	
	[inspector getFrame:&newFrame];
	[sender endEditingFor:nil];
	windowFrame.W = newFrame.W;
	windowFrame.X = newFrame.X;
	[entryForm	setAutosizing:entryFormMask];	//	@@@@ 1.1
	[scrollView	setAutosizing:scrollViewMask];
	[marker		setAutosizing:markerMask];


	return self;
}


- windowDidMove:sender
{
	NXRect	newFrame;
	
	[inspector getFrame:&newFrame];
	windowFrame.X = newFrame.X;
	windowFrame.Y =
		newFrame.Y + newFrame.H; // northwest_corner
	return self;
}


- textDidGetKeys:sender isEmpty:(BOOL)flag
{
if(0)fprintf( stderr, "sender is %s resp=%s, tag=%d\n",
	[sender name], [[inspector firstResponder]name], [[sender delegate] tag] );

	if( [[sender delegate] tag] == KEY_FIELD_TAG ){
		[self parseKey];
		if( theFile ) [self updateButtons];
		return self;
	}
	isDirty = YES;
	if( theFile ) [self updateButtons];

	// set the marker
	if( [[sender delegate] tag]==FORM_FIELD_TAG ){
		int	i = 0;
		char c;
		int	row = [[sender delegate] selectedRow];
		const char *theString = [[sender delegate] stringValueAt:row];

		if( theString==NULL ) return self;

		while( c=theString[i++] ){
			if( c=='\n' )break;
		}
		[self setMarker:c=='\n' at:row];
	}

	noteIsDirty = YES;
	return self;
}


- (BOOL)testSyntaxFor:(const char *)txt
	// returns NO, if this method has no objections
{
	char c;
	int	braceLevel = 0, paraLevel = 0;

	if( (txt==NULL) || (*txt=='\0') ) return NO;
	while( c=*txt++ ){
		if( c==LBRACE ) braceLevel++;
		else
		if( c==RBRACE ) braceLevel--;
		else
		if( c==LPARA ) paraLevel++;
		else
		if( c==RPARA ) paraLevel--;
	}
	if( braceLevel || paraLevel ) return YES;
	return NO;
}


- (BOOL)testSyntaxForStream:(NXStream *)str
	// returns NO, if this method has no objections
{
	char c;
	int	braceLevel = 0, paraLevel = 0;

	NXSeek( str, 0, NX_FROMSTART );

	while( (c = NXGetc( str ))!=EOF ){
		if( c==LBRACE ) braceLevel++;
		else
		if( c==RBRACE ) braceLevel--;
		else
		if( c==LPARA ) paraLevel++;
		else
		if( c==RPARA ) paraLevel--;
	}
	if( braceLevel || paraLevel ){
		NXBeep();
		return YES;
	}
	return NO;
}



- (BOOL)textWillEnd:sender
{
//fprintf( stderr, "textWillEnd sender is %s\n", [sender name] );
//fprintf( stderr, "textWillEnd delagte is %s\n", [[sender delegate] name] );
//fprintf( stderr, "textWillEnd tag is %d\n", [[sender delegate] tag] );
	if( [sender tag]==NOTE_TEXT_TAG ){
		if( noteIsDirty==NO ) return NO;
		// test text
		return [self testSyntaxForStream:[noteView stream]];
	}
	if( [[sender delegate] tag]==KEY_FIELD_TAG ){
		return NO;	// key is ok
	}
	if( [[sender delegate] tag]==FORM_FIELD_TAG ){
		int	row = [[sender delegate] selectedRow];
		// sender delegate is Form --> Matrix
		return [self testSyntaxFor:
			[[sender delegate] stringValueAt:row]];
	}
	return NO;
}


- XXXXtextDidEnd:sender endChar:(unsigned short)whyEnd
{
	int	i = 0;
	char *theString, c;

	if( [[sender delegate] tag]==FORM_FIELD_TAG ){
		int	row = [[sender delegate] selectedRow];

		theString = (char *)[[sender delegate] stringValueAt:row];
		if( theString==NULL ) return self;
		while( c=theString[i++] ){
			if( c=='\n' )break;
		}
		[self setMarker:c=='\n' at:row];
	}

	return self;
}


- (BOOL)shouldDelayWindowOrderingForEvent:(NXEvent *)theEvent
{
 	// This is the other portion of the new appkit functionality
	// which allows you to drag a partially obscure object without
	// making the source window become main, and without making the
	// source application the current app.
fprintf( stderr, "shouldDelayWindow\n" );
	return YES;
}


- (BOOL)acceptsFirstMouse
{
fprintf( stderr, "acceptsFirstMouse\n" );
	return YES;
}


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

	switch( [menuCell tag] ){
		case CUT			: newState = YES; break;
		case COPY			: newState = YES; break;
		case PASTE			: newState = YES; break;
		case SELECTALL		: newState = YES; break;
		case ENTERSELECTION : newState = YES; break;
		default : newState =
					[[[NXApp mainWindow] delegate]updateMenuFor:menuCell];
				  break;
	}
	return newState;
}




- setBacking:sender
{
	[inspector setBackingType:
		([sender state]==1) ? NX_BUFFERED :NX_RETAINED];
	return self;
}


@end


