#import "Fiend.h"
#import "ShelfView.h"
#import "IconView.h"
#import "IconDragView.h"
#import "ProgressView.h"
#import "Controller.h"
#import "compositeBackground.h"

#import <math.h>
#import <appkit/appkit.h>
#import <3Dkit/3Dkit.h>
#import <ansi/stdio.h>
#import <ansi/string.h>

@implementation ShelfView

- initFrame:(const NXRect *) aFrame
{
    int				screenCount;
    char			*backgroundString;
    NXScreen		*screens;
	const char		*fileString;
    const char		*colorString;
    const char 		*const types[1] = {NXFilenamePboardType};

	gridValue = atoi(NXGetDefaultValue([NXApp appName], SGRID_VALUE));
	gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");

    [super initFrame:aFrame];
    [[self window] setDelegate:self];
    [self registerForDraggedTypes:types count:1];

    /*
     *  Determine the background color.
     */
    useBGColor = NO;
    [NXApp getScreens:&screens count:&screenCount];

    if (screens[0].depth == NX_TwoBitGrayDepth)
		backgroundString = "BWBackgroundColor";
    else
		backgroundString = "BackgroundColor";

    colorString = NXGetDefaultValue("NeXT1", backgroundString);
    if (colorString) {
		float	r, g, b;
		sscanf(colorString, "%f %f %f", &r, &g, &b );
		bgColor = NXConvertRGBAToColor(r, g, b, NX_NOALPHA);
		useBGColor = YES;
    }

    /*
     *  Now that we've set up our view's appearance, do the rest of the
     *  initialization we need to do.
     */
	[window disableFlushWindow];
    [self readShelf:YES];
	tileImage = !strcmp(NXGetDefaultValue([NXApp appName], TILE_IMAGE), "YES");
	fileString = NXGetDefaultValue([NXApp appName], IMAGE_NAME);
	if (fileString)
		[self setImageFileName:fileString];
	[[window reenableFlushWindow] flushWindow];

    return self;
}


- free
{
	if (theImage != nil)
		[theImage free];
    return [super free];
}


- (BOOL) acceptsFirstMouse
{
    return YES;
}


- (NXColor) backgroundColor
{
    if (useBGColor)
		return bgColor;
    else
    	return NX_COLORLTGRAY;
}


- (BOOL) isAnyViewAt:(NXPoint) aPoint besides:aView
{
	id			testView;
    int			i;
	NXRect		rect;
	NXRect		interRect;
    int			max = [subviews count];

    for (i = 0;  i < max;  i++) {
		if ((testView = [subviews objectAt:i]) == aView)
			continue;

		[testView getFrame:&rect];
		NXSetRect(&interRect, aPoint.x, aPoint.y, gridValue, gridValue);
		if (NXIntersectionRect(&rect, &interRect))
			return YES;
    }

    return NO;
}


/*
 *  Align all of our IconViews on the grid.  Take care so that none overlap.
 */
- (void) alignSubviews
{
    int		i;
    int		max = [subviews count];

    for (i = 0;  i < max;  i++) {
		id	view = [[self subviews] objectAt:i];
		NXRect	rect;
		NXPoint candidatePt;
		int	count;

		if (![view isKindOf:[IconView class]])
			continue;

		/*
		 *  Make the icon the right size, and then compute the new origin.
		 */
		[view getFrame:&rect];
		candidatePt.x = gridValue * (int)(rect.origin.x/gridValue);
		candidatePt.y = gridValue * (int)(rect.origin.y/gridValue);

		count = bounds.size.height * bounds.size.width / gridValue;
		while (count-- > 0 && [self isAnyViewAt:candidatePt besides:view]) {
			candidatePt.x -= gridValue;
			if (candidatePt.x < 0) {
				candidatePt.x = gridValue;
				candidatePt.y -= gridValue;
				if (candidatePt.y < gridValue)	{
					candidatePt.y = gridValue;
					candidatePt.x -= gridValue;
				}
				
			}
		}

		[view moveTo:candidatePt.x :candidatePt.y];
    }

	[self writeShelf];
    [self display];
}


- (BOOL)tileImage
{
	return tileImage;
}

- setTileImage:(BOOL)flag
{
	tileImage = flag;
	NXWriteDefault([NXApp appName], TILE_IMAGE, (flag)?"YES":"NO");
	return self;
}

- (char *)getImageFileName
{
	return imageFileName;
}

- setImageFileName:(const char *)theName
{
	id			image;
	id			repList;
	int			i;
	NXSize		imageSize;
	NXImageRep	*rep;

	if (!theName || !strlen(theName))	{
		theImage = nil;
		imageFileName[0] = '\0';
	}
	else	{
		image = [[NXImage alloc] initFromFile:theName];
		if (image == nil)	{
			NXBeep();
			return nil;
		}
		[theImage free];
		theImage = image;

		[theImage getSize:&imageSize];
		if (!tileImage || NX_WIDTH(&frame) < imageSize.width ||
			NX_HEIGHT(&frame) < imageSize.height)	{
			[theImage setScalable:YES];
			[theImage setSize:&frame.size];
		}

		strcpy(imageFileName, theName);

		repList = [theImage representationList];
		for(i = 0; i < [repList count]; i++)	{
			rep = [repList objectAt:i];
			if ([rep isKindOf:[N3DRIBImageRep class]])	{
				[(N3DRIBImageRep *)rep setSurfaceType:N3D_FacetedSolids];
				break;
			}
		}
	}

    (void) NXWriteDefault([NXApp appName], IMAGE_NAME, imageFileName);
	[self display];

	return self;
}

- (BOOL) gridEnabled
{
    return gridEnabled;
}


- (void)setGridEnabled:(BOOL) flag
{
	gridEnabled = flag;
    (void) NXWriteDefault([NXApp appName], GRID_ENABLE, flag ? "YES" : "NO");
}


- (int)gridValue
{
	return gridValue;
}


- setGridValue:(int)aValue
{
    char gridString[20];

    if (aValue == gridValue)
    	return self;

	gridValue = aValue;

    if (gridValue < MIN_GRID_VALUE)
    	gridValue = MIN_GRID_VALUE;
    else if (gridValue > MAX_GRID_VALUE)
    	gridValue = MAX_GRID_VALUE;

    [window disableFlushWindow];
    [[self subviews] freeObjects];
    [IconView resetCachedShelfImages];
	[[self readShelf:NO] display];
	[[window reenableFlushWindow] flushWindow];

    sprintf(gridString, "%d", gridValue);
	NXWriteDefault([NXApp appName], SGRID_VALUE, gridString);

    return self;
}


- drawSelf:(const NXRect *) rects :(int) rectCount
{
	int		i;
	int		j;
	int		rectIndex;
	int		vertCount;
	int		horizCount;
	NXPoint	thePoint;
	NXRect	interRect;
	NXRect	theRect = {{0.0, 0.0}, {0.0, 0.0}};

    if (useBGColor) {
		NXSetColor(bgColor);
		NXRectFill(rects);
    }
    else
		compositeFromWorkspaceWindow(rects->origin.x, rects->origin.y,
									 rects->size.width, rects->size.height);

	if (theImage != nil)	{
		[theImage getSize:&theRect.size];
		vertCount = NX_HEIGHT(&frame)/NX_HEIGHT(&theRect);
		horizCount = NX_WIDTH(&frame)/NX_WIDTH(&theRect);
		for(rectIndex = 0; rectIndex < rectCount; rectIndex++)	{
			for(i = 0; i < horizCount+1; i++)	{
				for(j = 0; j < vertCount+1; j++)	{
					NXSetRect(&interRect, NX_WIDTH(&theRect)*i, NX_HEIGHT(&theRect)*j,
							  NX_WIDTH(&theRect), NX_HEIGHT(&theRect));
					if (NXIntersectionRect(&rects[rectIndex], &interRect))	{
						thePoint = interRect.origin;
						NX_X(&interRect) = fmod(NX_X(&interRect), NX_WIDTH(&theRect));
						NX_Y(&interRect) = fmod(NX_Y(&interRect), NX_HEIGHT(&theRect));
						[theImage composite:NX_SOVER fromRect:&interRect toPoint:&thePoint];
					}
				}
			}
		}
	}
    return self;
}


- deselectAll:sender
{
	NXRect		away = {{-100.0, -100.0}, {1.0, 1.0}};
	[self selectViewsInRect:&away deselect:YES];
    return NO;
}


- removeView:aView
{
    NXRect	viewFrame;

    [aView getFrame:&viewFrame];
    [aView removeFromSuperview];
    [self display:&viewFrame :1 :NO];
    return self;
}


- addView:aView
{
    NXRect	viewFrame;

    [aView getFrame:&viewFrame];
    [self addSubview:aView];
    [self display:&viewFrame :1 :NO];
    return self;
}


- deleteView:aView
{
    [self removeView:aView];
    [aView free];
    return self;
}


/*
 *  Return true if the point is in the area we use to get rid of views
 */
- (BOOL) isDeadZone:(NXPoint *) aPoint
{
    NXRect	goodZone = bounds;
    NXInsetRect(&goodZone, 2, 2);
    return !NXMouseInRect(aPoint, &goodZone, NO);
}


- (void) createViewForPath:(const char *) path at:(NXPoint *) point
{
    id				image;
    id				newView;
    struct stat		st;
    NXRect			aRect = {{0.0, 0.0}, {gridValue, gridValue}};

    if (stat(path, &st) < 0)
    	return;

	image = [IconView getImageForPath:path fileIcon:NO];
    newView = [[IconDragView alloc] initFrame:&aRect image:image
			   data:path andLength:strlen(path)+1 useSize:YES onDock:NO];

	aRect.origin = *point;

	[[newView setFrame:&aRect] display];

    [self addSubview:newView];
    [newView getFrame:&aRect];
	[self display:&aRect :1 :NO];
}


static BOOL
prefix(const char *prefix, const char *string)
{
    while (*prefix && *string && *prefix == *string) {
    	prefix++;
		string++;
    }
    return *prefix == '\0';
}


- (void) removeViewForPath:(const char *) fullPath
{
    int		i = [[self subviews] count];

    while (i > 0) {
    	char		*path;
		unsigned int	len;
		id		view = [[self subviews] objectAt:i];
		if ([view isKindOf:[IconView class]]) {
			[view getData:(void *) &path andLength:&len];
			if (prefix(fullPath, path))
				[self deleteView:view];
		}
		i--;
    }
}


/*
 *  Find the right position for the new image, based on the grid and the
 *  mouse's location.
 */
- (NXPoint) viewLocationForContext:(id <NXDraggingInfo>)dragContext
{
    NXPoint		newLoc;
    NXPoint		imagePt = [dragContext draggedImageLocation];
    NXPoint		mousePt = [dragContext draggingLocation];
    NXPoint		imageOffset;

    [draggedView getImagePoint:&imageOffset andHilitePoint:NULL];

    if (gridEnabled) {
    	NXRect	rect;
		[draggedView getFrame:&rect];

    	newLoc.x = mousePt.x - ((int) mousePt.x % gridValue) +
			(gridValue - rect.size.width) / 2;
		newLoc.y = mousePt.y - (int) mousePt.y % gridValue;
    }
    else {
		newLoc.x = imagePt.x - imageOffset.x;
		newLoc.y = imagePt.y - imageOffset.y;
    }
    return newLoc;
}


/*
 *  Make a ghost image to indicate that we're really a destination.
 */
- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
    NXSize	aSize;
    NXPoint	newLoc;

	if ([sender draggingSourceOperationMask] == NX_DragOperationNone)
		return NX_DragOperationNone;
	else if ([sender draggingSourceOperationMask] == NX_DragOperationPrivate)
		return NX_DragOperationNone;

	aSize.width = [self gridValue];
	aSize.height = aSize.width;
    draggedView = [[IconView allocFromZone:[self zone]]
				   initFromDragContext:sender andSize:&aSize onDock:NO];

	newLoc = [self viewLocationForContext:sender];
	[draggedView moveTo:newLoc.x :newLoc.y];

	[draggedView setGhost:YES];
	if (gridEnabled)
		[self addView:draggedView];

    return NX_DragOperationLink;
}


/*
 *  Move the dragged image, but only do it if we need to (that is, if the
 *  mouse moved to a new grid cell).
 */
- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
    NXPoint	mouseLoc;
    NXPoint	newLoc;
    NXRect	aFrame;

	if ([sender draggingSourceOperationMask] == NX_DragOperationNone)
		return NX_DragOperationNone;

	newLoc = [self viewLocationForContext:sender];
	if (!gridEnabled)
		return NX_DragOperationLink;

    /*
     *  If the icon was dragged off the edge, hide it somewhere!
     */
    mouseLoc = [sender draggingLocation];
    if ([self isDeadZone:&mouseLoc]) {
    	newLoc.x = -100;
		newLoc.y = -100;
    }

	if (gridEnabled)	{
		[draggedView getFrame:&aFrame];
		if (aFrame.origin.x != newLoc.x || aFrame.origin.y != newLoc.y) {
			[draggedView moveTo:newLoc.x :newLoc.y];

			[self display:&aFrame :1 :NO];		/* erase old */

			aFrame.origin = newLoc;
			[self display:&aFrame :1 :NO];		/* draw new */
		}
	}

    return NX_DragOperationLink;
}


/*
 *  Get rid of the resources we used to drag the image around.
 */
- draggingExited:(id <NXDraggingInfo>)sender
{
	if ([sender draggingSourceOperationMask] == NX_DragOperationNone)
		return NX_DragOperationNone;

	if (gridEnabled)
		[self removeView:draggedView];
    [draggedView free];

    return self;
}


/*
 *  Eat the result...
 */
- (BOOL) prepareForDragOperation:sender
{
    return YES;
}


- (BOOL) performDragOperation:sender
{
    NXPoint	mouseLoc;
    NXPoint	newLoc;

    /*

     *  If the dragged item landed in the dead zone, get rid of it.
	 *  Don't accept drag.
     */
	newLoc = [self viewLocationForContext:sender];
    mouseLoc = [sender draggingLocation];
    if ([self isDeadZone:&mouseLoc]) {
		[self removeView:draggedView];
		[draggedView free];
		draggedView = nil;
    	return NO;
    }

	[draggedView moveTo:newLoc.x :newLoc.y];
    return YES;
}


- selectViewsInRect:(NXRect *)theRect deselect:(BOOL)flag
{
	id		theView;
	int		i;
	NXRect	viewRect;
	NXPoint	thePoint;
	int		count = [subviews count];
	BOOL	found = NO;

	[window disableFlushWindow];
	for(i = 0; i < count; i++)	{
		theView = [subviews objectAt:i];
		if (theView == nil)
			continue;
		[theView getFrame:&viewRect];
		thePoint.x = NX_MIDX(&viewRect);
		thePoint.y = NX_MIDY(&viewRect);
		if (NXIntersectsRect(&viewRect, theRect))	{
			if (!flag)
				[theView setState:![theView state]];
			else
				[theView setState:YES];
			found = YES;
		}
		else if (flag)
			[theView setState:NO];
	}
	[[window reenableFlushWindow] flushWindow];

	if (found)
		[NXApp unhide:self];
	return self;
}

- nukeViewsInRect:(NXRect *)theRect
{
	id		theView;
	int		i;
	NXRect	viewRect;
	NXPoint	thePoint;
	int		count = [subviews count];
	id		killList = [[List alloc] init];
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");

	for(i = 0; i < count; i++)	{
		theView = [subviews objectAt:i];
		if (theView == nil)
			continue;
		[theView getFrame:&viewRect];
		thePoint.x = NX_MIDX(&viewRect);
		thePoint.y = NX_MIDY(&viewRect);
		if (NXIntersectsRect(&viewRect, theRect))
			[killList addObject:theView];
	}
	count = [killList count];
	for(i = 0; i < count; i++)
		[self deleteView:[killList objectAt:i]];
	[killList free];
	if (useSound)
		[[Sound findSoundFor:"Destroy"] play];

	[self writeShelf];
	return self;
}


- mouseDown:(NXEvent *)event
{
	BOOL		alt;
	BOOL		ctrl;
	BOOL		shift;
	BOOL		command;
	NXRect		theRect;
	NXPoint		p;
	NXPoint		origPt;
	BOOL		started = NO;
	int			mask = NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK;

	alt = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
	ctrl = (event->flags & NX_CONTROLMASK) ? YES : NO;
	shift = (event->flags & NX_SHIFTMASK) ? YES : NO;
	command = (event->flags & NX_COMMANDMASK) ? YES : NO;

	origPt = event->location;
	[window convertBaseToScreen:&origPt];

	if (event->data.mouse.click == 2)	{
		[NXApp unhide:self];
		return self;
	}

	[window addToEventMask:NX_MOUSEDRAGGEDMASK];
	do	{
		if (!started)	{
			[self lockFocus];
			PSsetgray(NX_BLACK);
			PSsetinstance(YES);
			started = YES;
		}

		p = event->location;
		NX_X(&theRect) = (origPt.x < p.x) ? origPt.x : p.x;
		NX_Y(&theRect) = (origPt.y < p.y) ? origPt.y : p.y;
		NX_WIDTH(&theRect) = fabs(origPt.x - p.x);
		NX_HEIGHT(&theRect) = fabs(origPt.y - p.y);
		PSnewinstance();
		NXFrameRect(&theRect);
		event = [NXApp getNextEvent:mask];
	} while (event && event->type != NX_MOUSEUP);


	if (started)	{
		PSnewinstance();
		PSsetinstance(NO);
		[self unlockFocus];
		PSsetgray(NX_BLACK);
		if (alt)
			[self nukeViewsInRect:&theRect];
		else	{
			[[NXApp delegate] deselectDock];
			[self selectViewsInRect:&theRect deselect:!shift];
		}
	}

	return self;
}

/*
 *  Actually write the stuff way down here.  It's completely at the end
 *  of the operation, so a slow write won't hose the UI.
 */
- concludeDragOperation:(id <NXDraggingInfo>)sender
{
	int			len;
	int			horizLimit;
	int			horizCount;
	char		*ptr;
	char		*path;
	char		*dragString;
	NXRect		newFrame;
	NXRect		viewFrame;
	NXPoint 	newPoint;
	BOOL		useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");

	[draggedView getFrame:&viewFrame];
	newFrame = viewFrame;
	newPoint = viewFrame.origin;

	[draggedView getData:(void **)&dragString andLength:&len];
	horizCount = 0;
	horizLimit = 1000;
	if ([sender draggingSourceOperationMask] & NX_DragOperationLink)	{
		horizLimit = 0;
		ptr = dragString;
		while(*ptr)	{
			if (*ptr == '\t')
				horizLimit++;
			ptr++;
		}
		horizLimit = (int)sqrt((float)horizLimit);
	}

	path = strtok(dragString, "\t");
	while(path && strlen(path))	{
		while([self isAnyViewAt:newPoint besides:draggedView])	{
			if (newPoint.x >= gridValue && horizCount < horizLimit)	{
				newPoint.x -= gridValue;
				horizCount++;
			}
			else	{
				horizCount = 0;
				newPoint.x = NX_X(&viewFrame);
				if (newPoint.y >= gridValue && newPoint.y <= NX_Y(&viewFrame))
					newPoint.y -= gridValue;
				else if (newPoint.y >= gridValue)
					newPoint.y += gridValue;
				else
					newPoint.y = NX_Y(&viewFrame) + gridValue;
			}
		}

		newFrame.origin = newPoint;
		[self createViewForPath:path at:&newFrame.origin];
		NXPing();
		path = strtok(NULL, "\t");
	}

	if (useSound && ![sender isDraggingSourceLocal])
		[[Sound findSoundFor:"Fiend"] play];

	if (draggedView != nil)	{
		[self removeView:draggedView];
		[draggedView free];
	}
	[self writeShelf];
    return self;
}


/*
 *  Be a drag source, too
 */
- setDragView:aView onEvent:(NXEvent *) e withOffset:(NXPoint *) offset
   atLocation:(const NXPoint *) location
{
    id			pb = [Pasteboard newName:NXDragPboard];
    void		*data;
    unsigned int	length;
    NXPoint		myLoc;

    /*
     *  Initiate a drag operation.  Copy stuff into the pasteboard,
     *  then start dragging.  To simplify matters elsewhere, we try
     *  to make a local dragging operation look just like a non-local
     *  one.
     */
    dragSourceView = aView;
    keepSourceOnShelf = (e->flags & NX_SHIFTMASK) ? YES : NO;

    [aView getData:&data andLength:&length];
    [pb declareTypes:&NXFilenamePboardType num:1 owner:nil];
    [pb writeType:NXFilenamePboardType data:data length:length];

    myLoc = *location;
    [aView convertPoint:&myLoc toView:self];
    [self dragImage:[aView image] at:&myLoc
	 offset:offset event:e pasteboard:pb
	 source:self slideBack:YES];

    return self;
}


- draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint
{
    NXRect	theFrame;

    [dragSourceView getFrame:&theFrame];
    [self display:&theFrame :1 :NO];
    if (!keepSourceOnShelf)
		[self removeView:dragSourceView];

    return self;
}


/*
 *  A drag operation, with us as the source, finished.  If it was an
 *  unsuccessful drag then, put the source back!  If it was a successful
 *  drag, and we weren't the destination, then remove the thing from the
 *  shelf.
 */
- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
     deposited:(BOOL)didDeposit
{
    char		*path;
    unsigned int	len;
    struct stat		st;

    /*
     *  Check to see if we should keep the source dir on the shelf.  We
     *  do this if the keepSourceOnShelf flag is set, and if the file
     *  under the icon still exists.
     */
    [dragSourceView getData:(void **) &path andLength:&len];
    if (keepSourceOnShelf && path && stat(path, &st) == 0) {
		keepSourceOnShelf = NO;
		return self;
    }

    /*
     *  The source isn't on the screen, so either get rid of the source, or
     *  put it back.
     */
    if (didDeposit)
    	[self deleteView:dragSourceView];
    else
    	[self addView:dragSourceView];

    return self;
}


- (NXDragOperation) draggingSourceOperationMaskForLocal:(BOOL)flag
{
    return NX_DragOperationAll;
}


- (BOOL)hasSelectedCells
{
	int		i;
	int		count = [subviews count];

	for(i = 0; i < count; i++)	{
		if ([[subviews objectAt:i] state])
			return YES;
	}
	return NO;
}

- copy:sender andCut:(BOOL)cutFlag
{
	id		aView;
	int		i;
	int		junk;
	char	*viewPath;
	char	cutString[10000];
	int		count = [subviews count];
	id		pb = [Pasteboard newName:NXGeneralPboard];
	id		cutList = [[List alloc] init];
	const char *types[] = {NXFilenamePboardType, NXAsciiPboardType};
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");

	*cutString = '\0';
	for(i = 0; i < count; i++)	{
		aView = [subviews objectAt:i];
		if ([aView state])	{
			[aView getData:(void **)&viewPath andLength:&junk];
			strcat(cutString, viewPath);
			strcat(cutString, "\t");
			[cutList addObject:aView];
		}
	}

	cutString[strlen(cutString)-1] = '\0';
	[pb declareTypes:types num:2 owner:nil];
	[pb writeType:NXFilenamePboardType data:cutString length:strlen(cutString)];
	for(i = 0; i < strlen(cutString); i++)	{
		if (cutString[i] == '\t')
			cutString[i] = '\n';
	}
	strcat(cutString, "\n");
	[pb writeType:NXAsciiPboardType data:cutString length:strlen(cutString)];

	if (cutFlag)	{
		if (useSound)
			[[Sound findSoundFor:"Destroy"] play];
		count = [cutList count];
		for(i = 0; i < count; i++)
			[self deleteView:[cutList objectAt:i]];
	}

	[cutList free];
	[self writeShelf];
	return self;
}

- paste:sender
{
	int		len;
	int		horizLimit;
	int		horizCount;
	char	*ptr;
	char	*path;
	char	*dragString;
	BOOL	ctrl;
	NXRect 	newFrame;
	NXEvent	*event;
	NXPoint	newPoint;
	NXPoint	locus;
	id		pb = [Pasteboard newName:NXGeneralPboard];
	NXRect	viewFrame = {{0.0, 0.0}, {gridValue, gridValue}};
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");

	[pb readType:NXFilenamePboardType data:&dragString length:&len];
	if (!len)
		return self;

	[NXIBeam push];
	do	{
		event = [NXApp getNextEvent:NX_MOUSEDOWNMASK|NX_MOUSEDRAGGEDMASK];
	} while (event && event->type != NX_MOUSEDOWN);
	ctrl = (event->flags & NX_CONTROLMASK) ? YES : NO;
	[NXIBeam pop];

	locus = event->location;
	locus.x = locus.x - (int)locus.x % gridValue;
	locus.y = locus.y - (int)locus.y % gridValue;

	newPoint = locus;
	viewFrame.origin = locus;
	newFrame = viewFrame;

	horizCount = 0;
	horizLimit = 10000;
	if (len && !ctrl)	{
		horizLimit = 0;
		ptr = dragString;
		while(*ptr)	{
			if (*ptr == '\t')
				horizLimit++;
			ptr++;
		}
		horizLimit = (int)sqrt((float)horizLimit);
	}
	[window disableFlushWindow];
	path = strtok(dragString, "\t");
	while(path && strlen(path))	{
		while([self isAnyViewAt:newPoint besides:nil])	{
			if (newPoint.x >= gridValue && horizCount < horizLimit)	{
				newPoint.x -= gridValue;
				horizCount++;
			}
			else	{
				horizCount = 0;
				newPoint.x = NX_X(&viewFrame);
				if (newPoint.y >= gridValue && newPoint.y <= NX_Y(&viewFrame))
					newPoint.y -= gridValue;
				else if (newPoint.y >= gridValue)
					newPoint.y += gridValue;
				else
					newPoint.y = NX_Y(&viewFrame) + gridValue;
			}
		}
		newFrame.origin = newPoint;
		[self createViewForPath:path at:&newFrame.origin];
		NXPing();
		path = strtok(NULL, "\t");
	}
	[[window reenableFlushWindow] flushWindow];
	if (useSound)
		[[Sound findSoundFor:"Fiend"] play];

	[pb deallocatePasteboardData:dragString length:len];
	return self;
}

/*
 *  Open the shelf file.
 */
- (FILE *) openShelfFor:(char *) how
{
    char	path[MAXPATHLEN];

    sprintf(path, "%s/%s", NXHomeDirectory(), FIENDSHELF_FILE);
    return fopen(path, how);
}


/*
 *  Close it.
 */
- closeShelf:(FILE *) file
{
    fclose(file);
    return self;
}


/*
 *  Read the contents of the shelf in from a file.  The file's format consists
 *  of lines of the form:
 *
 *	x y path
 *
 *  where the two numbers x,y specify the origin of the particular view on the
 *  shelf, and path specifies the path to the workspace.  Somewhat bogusly,
 *  we assume the path starts at character 14.
 */
- readShelf:(BOOL)showProgress
{
	float	i;
	float	count;
    FILE	*file;
    char	line[MAXPATHLEN + 30];
    char	*path;
    NXPoint	point;

    file = [self openShelfFor:"r"];
    if (file == NULL)
		return self;

	count = 0.0;
	if (showProgress)	{
		while(fgets(line, sizeof(line), file))
			count++;
		rewind(file);
		[[NXApp delegate] setProgressViewRatio:0.0];
	}

	i = 1.0;
    while (fgets(line, sizeof(line), file)) {
		if (showProgress)
			[[NXApp delegate] setProgressViewRatio:0.9*i++/count];

		/*
		 *  Parse the line in the shelf.  It's too bad that we can't use
		 *  sscanf to parse the whole line!
		 */
		sscanf(line, "%f %f", &point.x, &point.y);

		/* file string starts after second number, char 14 */
		if (strlen(line) > 14) {
			path = line + 14;
			if (rindex(path, '\n') != NULL)
				*rindex(path, '\n') = '\0';
		}
		else
			continue;

		/*
		 *  Make a spot for this guy...
		 */
		[self createViewForPath:path at:&point];
    }

    [self closeShelf:file];

    return self;
}


/*
 *  Write the contents of the shelf out to the shelf file.
 */
- writeShelf
{
    FILE	*file;
    NXRect	rect;
    int		i;

    file = [self openShelfFor:"w"];
    if (file == NULL)
		return self;

    for (i = 0;  i < [[self subviews] count];  i++) {
		id		view = [[self subviews] objectAt:i];
		unsigned int	length;
		char		*path;

		if (![view isKindOf:[IconView class]] || [view isOnRemovableMedia])
			continue;

		[view getData:(void **) &path andLength:&length];
		[view getFrame:&rect];

		fprintf(file, "%6.0f %6.0f %s\n", rect.origin.x, rect.origin.y, path);
    }

    [self closeShelf:file];
    return self;
}

@end
