

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

#import "Shelf.h"
#import "Controller.h"
#import "DragView.h"
#import "BibliographicFile.h"
#import "Preferences.h"


#define DRAGVIEWTAG	7
#define W_SPACE		10.0
#define H_SPACE		10.0
#define L_MARGIN	5.0


@implementation Shelf


- initFrame:(const NXRect *)frameRect
/*
%initFrame% initializes the view and registers the Pasteboard types that the shelf will accept in an image-dragging session. The shelf accepts only the NXFilenamePboardType.
Returns self.
*/
{
	[self registerForDraggedTypes:&NXFilenamePboardType count:1];
	MyListPboardType = NXUniqueString( "My List Pboard Type" );

	[super initFrame:frameRect];
	[self setFlipped:YES];
	preferences = [Preferences new];
	isSelected = NO;
	theImage = [NXImage findImageNamed:"Images/SmallBooks"];

	boxList = [[List alloc] init];
	return  self;
}


- (void) setBoxPrototype:(Box *)aBox
/* New boxes for the shelf are generated from %aStream% by calling
the function NXReadObjectFromBuffer() using the buffer of %aStream%.
See also readBoxFromStream.
*/
{
	NXTypedStream	*masterTypedStr;
	int		streamLength;
	int		streamMaxLength;
	char	*streamBuffer;

	[aBox getFrame:&boxRect];

	protypeStream = NXOpenMemory( NULL, 0, NX_WRITEONLY);
	masterTypedStr = NXOpenTypedStream(protypeStream, NX_WRITEONLY);
	NXWriteRootObject( masterTypedStr, aBox );
	NXCloseTypedStream( masterTypedStr );
	NXGetMemoryBuffer(protypeStream,
			&streamBuffer, &streamLength, &streamMaxLength);
	NXCloseMemory(protypeStream, NX_TRUNCATEBUFFER);
	protypeStream = NXOpenMemory( streamBuffer, streamLength, NX_READONLY);

}


- (BOOL)loadShelfFromFile:(const char *)file
{
	int		newInx;
	int		okFlag;
	Box		*newBox;
	char	buffer[1024];
	char 	*first, *last, *comment;
	FILE	*fp;

	fp = fopen( file, "r" );

	if( fp==NULL ) return NO;
	if( protypeStream==NULL ) return NO;

	while( fgets( buffer, 512, fp ) ){
		first = strchr( buffer, '"');	// first occurence of '"'
		if( first==NULL ) continue;
		last = strrchr( buffer, '"');	// last occurence of '"'
		if( last==NULL ) continue;
		*last = '\0';
		// commentline ?
		comment = strchr( buffer, '%');	// first occurence of '%'
		if( comment && (comment<first) ) continue;
		// filename is ok
		newBox = [self readBoxFromStream:protypeStream];

		newInx = [self insertBox:newBox];
		[[newBox findViewWithTag:DRAGVIEWTAG] setFilename:first+1];
		last++;		//first character behind '\0', was '"'
		while( *last ){
			int	rtn;
			if( *last=='+' ){	// open the file
				shelfBox[newInx].state = 1;
				rtn = [NXApp openFile:first+1 ok:&okFlag];
				break;
			}
			if( *last=='-' ) break;
			last++;
		}
	}
	fclose( fp );
	return YES;
}


- setBackgroundColor:(NXColor)color
{
	[[self superview] setBackgroundColor:color];
	[[self superview] display];
	return self;
}


- setBackgroundGray:(float)gray
{
	[[self superview] setBackgroundGray:gray];
	//[[self superview] display];
	[self display];
	return self;
}


/***********	find box or empty slot	************/

- (int)indexOfBox:(Box *)box
{
	int	j;

	for( j=0; j<count; j++ ){
		if( shelfBox[j].theBox == box ) return j;
	}
	return -1;
}


- (int)indexOfBoxWithIcon:(DragView *)icon
{
	return [boxList indexOf:icon];
}


- (int)indexOfBoxForFile:(BibliographicFile *)aFile
{
	//DragView	*theView;
	int	j;

	for( j=0; j<count; j++ ){
		//theView = [boxList objectAt:j];
		if( shelfBox[j].theFile == aFile ) return j;
	}
	return -1;
}


- (int)indexOfBoxNamed:(const char *)fileName
{
	DragView	*theView;
	int	j;

	for( j=0; j<count; j++ ){
		theView = [boxList objectAt:j];
		if( [theView fileName]==NULL ) continue;
		if( strcmp([theView fileName], fileName)==0 ) return j;
	}
	return -1;
}


- (int)indexOfEmptySlot
{
	int	j;

	for( j=0; j<count; j++ ){
		if( shelfBox[j].theBox==nil ) return j;
	}
	return -1;
}


/*****	 properties of an icon		****/

- (const char *)filenameOfIconAt:(int)inx
{
	if( (inx<0) || (inx>=count) ) return NULL;
	if( shelfBox[inx].theBox==nil ) return NULL;

	return [[boxList objectAt:inx] fileName];
}


- (int)getstateOfIconAt:(int)inx
{
	if( (inx<0) || (inx>=count) ) return -1;
	return shelfBox[inx].state;
}


- setState:(int)state forIconAt:(int)inx
{
	if( (inx<0) || (inx>=count) ) return nil;
	shelfBox[inx].state = state;
	return self;
}



/********		notification methods	*******/

static const char * dragTypes[3];

- attachFile:(const char *)fName toObject:(BibliographicFile *)obj
{
	DragView	*theView;
	int j = [self indexOfBoxNamed:fName];
	if( j<0 ) return nil;

	theView = [boxList objectAt:j];
	[theView setFileObject:obj];

	dragTypes[0] = MyListPboardType;
	dragTypes[1] = NXAsciiPboardType;
	[theView registerForDraggedTypes:dragTypes count:2];
	shelfBox[j].theFile = obj;
	[theView display];
	return self;
}


- attachObject:(BibliographicFile *)obj toFile:(const char *)fName
{
	DragView	*theView;
	int j = [self indexOfBoxForFile:obj];
	if( j<0 ) return nil;

	theView = [boxList objectAt:j];
	[theView setFileObject:obj];
	[theView display];

	[preferences shelfDidChange];
	return self;
}


- detachObject:(BibliographicFile *)obj
{
	int j = [self indexOfBoxForFile:obj];
	if( j<0 ) return nil;

	[[boxList objectAt:j] unregister];
	shelfBox[j].theFile = nil;

	return self;
}



/***********	remove box identified by ...	************/


- (void) removeBoxAt:(int)inx
{
	DragView	*theView;

	if( inx<0 ) return;

	theView = [boxList objectAt:inx];
	[theView clear];
	[theView unregisterDraggedTypes];

	[shelfBox[inx].theBox removeFromSuperview];
	[NXApp delayedFree:shelfBox[inx].theBox];
	shelfBox[inx].theBox = nil;
	shelfBox[inx].theFile = nil;

	[boxList removeObjectAt:inx];
	[self redrawShelf];
	return;

	if( inx==count-1 ){	// determine last box and compact
		while( count && (shelfBox[count-1].theBox==nil) ) count--;
		[self redrawShelf];
	}
	else [self redrawShelf];	// compact
	//else [self display];		// does not compact
}


- (void) removeBoxWithIcon:(DragView *)icon
{
	[self removeBoxAt:[self indexOfBoxWithIcon:icon]];
	[self display];
	[preferences shelfDidChange];
}


- (Box *) readBoxFromStream:(NXStream *)theStream
{
	char	*buffer;
	int		len, maxlen;
	Box		*box;

	NXGetMemoryBuffer(theStream, &buffer, &len, &maxlen);
	box = NXReadObjectFromBuffer( buffer, len);

	[box getFrame:&boxRect];
	[box setBorderType:NX_NOBORDER];
	
	return box;
	
}


- (void) sizeShelf:(int)cnt
{
	NXRect	r;

	if( scrollView==nil ){	// first call
		needsHorizontalScroller = YES;	//1.2 select type of scroller

		scrollView = [[self superview] superview];
		[scrollView setVertScrollerRequired:!needsHorizontalScroller];
		[scrollView setHorizScrollerRequired:needsHorizontalScroller];

		if( needsHorizontalScroller )
				[[scrollView setVertScroller:nil] free];
		else	[[scrollView setHorizScroller:nil] free];
		
		// set size (this is the first call)
		[[self superview] getFrame:&r];
		[self sizeTo:r.W :r.H];
	}

	[[self superview] getFrame:&r];

	if( needsHorizontalScroller ){	// horizontal
		NXCoord width, height;
		iconsPerRow = cnt;
		width = iconsPerRow*(boxRect.W + W_SPACE);
		height = boxRect.H + H_SPACE;
		[self sizeTo:( (width<r.W)?r.W:width )
					:( (height<r.H)?r.H:(boxRect.H + H_SPACE) )];
		[scrollView setLineScroll:(boxRect.W + W_SPACE)];
	}
	else{
		iconsPerRow = (int)((r.W - L_MARGIN + W_SPACE)/(boxRect.W + W_SPACE));
		[self sizeTo:r.W
					:(((cnt-1)/iconsPerRow)+1)*(boxRect.H + H_SPACE)];
		[scrollView setLineScroll:(boxRect.H + H_SPACE)];
	}
}


- (void) setCoordinates:(NXRect *)r at:(int)inx
{
	if( inx<0 ) { r->X = W_SPACE; r->Y = H_SPACE; return; }

	if( needsHorizontalScroller ){	// horizontal
		r->X = (inx)*(r->W + W_SPACE) + L_MARGIN;
		r->Y = 0;
	}
	else{
		r->X = (inx%iconsPerRow)*(r->W + W_SPACE) + L_MARGIN;
		r->Y = (inx/iconsPerRow)*(r->H + H_SPACE);
	}
}


- (void) redrawShelf
{
	Box		*box;
	int		in, out;

	[self sizeShelf:count];
	for( in=0, out=0; in<count; in++){
		box =  shelfBox[in].theBox;
		if( box==nil ) continue;
		[self setCoordinates:&boxRect at:out];
		[box setFrame:&boxRect];
		shelfBox[out] = shelfBox[in];
		out++;
	}
	if( count==out ){
		[[self superview] display];
		return;
	}
	count = out;
	[self sizeShelf:count];
	[[self superview] display];
	[preferences shelfDidChange];
}


- (int)insertBox:(Box *)box at:(int)inx
{
	DragView	*newView;

	if( (inx<0) || (inx>=MAXCOUNT) ){
		fprintf( stderr, "internal error, invalid shelf index %d\n", inx );
		return -1;
	}

	[self setCoordinates:&boxRect at:inx];
	[box setFrame:&boxRect];

	newView = [box findViewWithTag:DRAGVIEWTAG];
	[boxList addObject:newView];
	[newView setShelf:self];
	shelfBox[inx].theBox = box;
	shelfBox[inx].theFile = nil;
	shelfBox[inx].state = 0;

	[self addSubview:box];
	[self scrollRectToVisible:&boxRect];
	[preferences shelfDidChange];
	return inx;
}


- (int)insertBox:(Box *)box
{
	int	inx = [self indexOfEmptySlot];

	if( inx>=0 ){
		[self insertBox:box at:inx];
	}
	else {
		if( count>=MAXCOUNT ){
			fprintf( stderr, "too many icons\n" );	// <---------
			return -1;
		}
		count++;
		[self sizeShelf:count];
		inx = [self insertBox:box at:count-1];	// last entry in table
	}
	[self display];

	return inx;
}


- (int)count
{
	return count;
}


- windowDidResize:sender
{
	[self redrawShelf];
	return self;
}


/************	methods used while dragging	   ************/


- drawSelf:(const NXRect *)rects :(int)rectCount
{
	NXRect	shadowBoxRect = boxRect;
	NXPoint	zero = {0.0, 0.0 };

	if( isSelected && (theImage!=nil) ){
		NXImage *tmpImage = [[NXImage alloc] initSize:&boxRect.size];
		if( [tmpImage lockFocus] ){
			PSsetgray( NX_LTGRAY );
			PSrectfill(0.0, 0.0, boxRect.size.width, boxRect.size.height);
			shadowBoxRect.origin.x += 14;
			shadowBoxRect.origin.y +=shadowBoxRect.size.height - 16;
			[theImage dissolve:0.6 toPoint:&zero];
			[tmpImage unlockFocus];
			[tmpImage composite:NX_SOVER toPoint:&shadowBoxRect.origin];
			[tmpImage free];
		}
		else{
			fprintf( stderr, "cannot lockFocus\n" );
		}
	}
	return self;
}



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

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

static BOOL includesType (const NXAtom *types, NXAtom type)
{
    if (types)
		while (*types){ if (*types++ == type) return YES; }
    return NO;
}


- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
	int	inx;
	NXDragOperation	rtn = [self draggingUpdated:sender];

	if( rtn!=NX_DragOperationGeneric ) return rtn;

	inx = [self indexOfEmptySlot];
	if( inx<0 ){
		[self sizeShelf:count+1];
		inx = count;
	}

	[self setCoordinates:&boxRect at:inx];
	isSelected = YES;
	theImage = [sender draggedImage];
	[self scrollRectToVisible:&boxRect];
	[self display];
	return rtn;
}


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

	if (includesType([pboard types], MyListPboardType)) {
		return NX_DragOperationNone;
	}

    if( ([sender draggingSourceOperationMask] & NX_DragOperationGeneric) 
	  ||([sender draggingSourceOperationMask] & NX_DragOperationCopy) ){
	  
		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 ){ 
					if( strcmp( lastDot, ".bib" ) ) {	// only .bib allowed
						return NX_DragOperationNone;
					}
				}
				else return NX_DragOperationNone;	// no suffix -> invalid
				// is the file already on the shelf ?
				if( [self indexOfBoxNamed:file]>=0 ){	// yes
					return NX_DragOperationNone;
				}
				file = (tab) ? ++tab : NULL;
			}
			return NX_DragOperationGeneric;
		}
    }	
    return NX_DragOperationNone;	    
}


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

	Pasteboard *pboard = [sender draggingPasteboard];

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

		file = data;
		while (file) {
			Box					*newBox;
			DragView			*newView;
			BibliographicFile	*fileObj;

			if (tab = strchr(file, '\t')) *tab = '\0';
			newBox = [self readBoxFromStream:protypeStream];
			newView = [newBox findViewWithTag:DRAGVIEWTAG];
			[newView setFilename:file];
			[self insertBox:newBox];
			// ask if this file is open in this application
			fileObj = [[NXApp delegate] objectForFileNamed:file];
			if( fileObj )[self attachFile:file toObject:fileObj];
			file = (tab) ? ++tab : NULL;
		}
		return YES;
	}

	return NO;
}


- draggingExited:sender
{
	isSelected = NO;
	[self redrawShelf];
    return self;
}


@end
