#import "Fiend.h"
#import "ShelfView.h"
#import "IconView.h"
#import "IconDragView.h"
#import "ProgressView.h"
#import "Controller.h"
#import "Dock.h"
#import "DockMgrView.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));

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

    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;
    }

	fiendSound = [Sound findSoundFor:"Fiend"];
	destroySound = [Sound findSoundFor:"Destroy"];

//	[window disableFlushWindow];
    [self readShelf:YES];
	
	tileImage = -1;
	fileString = NXGetDefaultValue([NXApp appName], IMAGE_NAME);
	[self setImageFileName:fileString];
	[self setTileImage:!strcmp(NXGetDefaultValue([NXApp appName], TILE_IMAGE), "YES")];
//	[[window reenableFlushWindow] flushWindowIfNeeded];

    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 useSlimMargin:(BOOL)flag
{
	id			*testView;
    int			i;
	NXRect		rect;
    int			count = [subviews count];
	id			*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;
	NXRect		interRect = {{aPoint.x, aPoint.y}, {gridValue, gridValue}};

	if (!flag)
		NXInsetRect(&interRect, 0.35*gridValue, 0.35*gridValue);
    for (i = 0, testView = viewListPtr;  i < count;  i++, testView++) {
		if (*testView == aView)
			continue;
		[*testView getFrame:&rect];
		if (NXIntersectsRect(&rect, &interRect))
			return YES;
    }

    return NO;
}


- (void)alignSubviews
{
	id		*view;
    int		i;
    int		viewCount = [subviews count];
	id		*viewListPtr = (viewCount > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	[window disableFlushWindow];
    for (i = 0, view = viewListPtr;  i < viewCount;  i++, view++) {
		NXRect	rect;
		NXPoint candidatePt;
		int	count;

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

		[*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 useSlimMargin:NO]) {
			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];
		[*view setState:NO];
    }

    [self display];
	[[window reenableFlushWindow] flushWindowIfNeeded];
	[self writeShelf];
}


- (BOOL)tileImage
{
	return tileImage;
}

- setTileImage:(BOOL)flag
{
	NXSize		imageSize;

	if (flag != tileImage)	{
		tileImage = flag;
		if (theImage)	{
			[theImage getSize:&imageSize];
			if (!tileImage && (NX_WIDTH(&frame) != imageSize.width || NX_HEIGHT(&frame) != imageSize.height))
				[theImage setSize:&frame.size];
			else if (tileImage && (imageSize.height != origImageSize.height || imageSize.width != origImageSize.width))
				[theImage setSize:&origImageSize];
		}
		[self update];
	}

	return self;
}

- (char *)getImageFileName
{
	return imageFileName;
}

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

	if (!theName || !strlen(theName))	{
		theImage = nil;
		imageFileName[0] = '\0';
		NXWriteDefault([NXApp appName], IMAGE_NAME, "");
	}
	else if (strcmp(imageFileName, theName))	{
		image = [[NXImage allocFromZone:[self zone]] initFromFile:theName];
		strcpy(imageFileName, theName);
//		NXWriteDefault([NXApp appName], IMAGE_NAME, imageFileName);

		if (image != nil)	{
			if ([image lockFocus])	{
				[image unlockFocus];
				if (theImage != nil)
					[theImage free];
				theImage = image;
			}
			else
				return nil;
		}
		else
			return nil;

		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;
			}
		}
		[theImage setScalable:YES];
		[theImage getSize:&origImageSize];
		if (!tileImage)
			[theImage setSize:&frame.size];
	}

	[self update];

	return self;
}

- (BOOL) gridEnabled
{
	return !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");
}


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


- (int)gridValue
{
	return gridValue;
}


- setGridValue:(int)aValue
{
    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;

//	gridChanged = YES;
	[window disableFlushWindow];
    [[self subviews] freeObjects];
    [IconView resetCachedShelfImages];
	[[self readShelf:NO] display];
	[[window reenableFlushWindow] flushWindowIfNeeded];

    return self;
}

- drawSelf:(const NXRect *) rects :(int) rectCount
{
	int		i;

	int		j;
	int		rectIndex;
	int		vertCount;
	int		horizCount;
	NXPoint	thePoint;
	NXRect	interRect;
	BOOL	compositeShelf = !strcmp(NXGetDefaultValue([NXApp appName], CPST_SHLF), "YES");
	NXRect	theRect = {{0.0, 0.0}, {0.0, 0.0}};

	if (theImage != nil)	{
		NXSetColor(bgColor);
		NXRectFill(rects);
		[theImage getSize:&theRect.size];
		vertCount = NX_HEIGHT(&frame)/NX_HEIGHT(&theRect);
		horizCount = NX_WIDTH(&frame)/NX_WIDTH(&theRect);
		rectIndex = (rectCount == 3) ? 1 : 0;
		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];
					}
				}
			}
		}
	}
	else if (useBGColor && !compositeShelf) {
		NXSetColor(bgColor);
		NXRectFill(rects);
    }
    else
		compositeFromWorkspaceWindow(rects->origin.x, rects->origin.y,
									 rects->size.width, rects->size.height);

    return self;
}


- deselectAll:sender
{
	id		*view;
	int		i;
	int		count = [subviews count];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	[window disableFlushWindow];
	for(i = 0, view = viewListPtr; i < count; i++, view++)	{
		if ([*view state])
			[*view setState:NO];
	}
	[[window reenableFlushWindow] flushWindowIfNeeded];

	[[NXApp delegate] updateMenus];
    return self;
}


- 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 withImage:(NXImage *)image at:(NXPoint *)point
{
    id				newImage;
    id				newView;
	BOOL			drawThumbnail = !strcmp(NXGetDefaultValue([NXApp appName], DRAW_TNAIL), "YES");
    NXRect			aRect = {{0.0, 0.0}, {gridValue, gridValue}};

	if (image == nil)
		newImage = [IconView getImageForPath:path fileIcon:!drawThumbnail zone:[self zone]];
	else
		newImage = [image copyFromZone:[self zone]];
    newView = [[IconDragView allocFromZone:[self zone]] initFrame:&aRect image:newImage
			   data:path andLength:strlen(path)+1 useSize:YES onDock:NO];

	aRect.origin = *point;
	if (NX_MAXX(&aRect) > NX_WIDTH(&frame))
		NX_X(&aRect) = fmod(point->x, NX_WIDTH(&frame)-gridValue);
	if (NX_MAXY(&aRect) > NX_HEIGHT(&frame))
		NX_Y(&aRect) = fmod(point->y, NX_HEIGHT(&frame)-gridValue);
	[[newView setFrame:&aRect] display];

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


- (NXPoint)viewLocationForContext:(id <NXDraggingInfo>)dragContext
{
    NXPoint		newLoc;
    NXPoint		imageOffset;
    NXPoint		imagePt = [dragContext draggedImageLocation];
    NXPoint		mousePt = [dragContext draggingLocation];
	BOOL		gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");

    [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;
}


- createDraggedView:(id <NXDraggingInfo>)sender
{
    NXSize	aSize;
    NXPoint	newLoc;
	BOOL	gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");

	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 self;
}

- (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
{
	id				source = [sender draggingSource];
	BOOL			localSrc = [sender isDraggingSourceLocal];
	BOOL			strict = !strcmp(NXGetDefaultValue([NXApp appName], SSTRICT_COPY), "YES");
	NXDragOperation	mask = [sender draggingSourceOperationMask];

	if (mask == NX_DragOperationPrivate || (localSrc && [source isKindOf:[IconView class]]) ||
		(!localSrc &&
		 ((strict && mask != NX_DragOperationCopy) ||
		  (!strict && !(mask & NX_DragOperationCopy)))))	{

		draggedView = nil;
		return NX_DragOperationNone;
	}

	[self createDraggedView:sender];
	return (mask == NX_DragOperationCopy) ? NX_DragOperationCopy :
		((localSrc)?NX_DragOperationGeneric:NX_DragOperationCopy);
}


- (NXDragOperation)draggingUpdated:(id <NXDraggingInfo>)sender
{
    NXRect			aFrame;
    NXPoint			newLoc;
	id				source = [sender draggingSource];
	BOOL			localSrc = [sender isDraggingSourceLocal];
	NXDragOperation	mask = [sender draggingSourceOperationMask];
	BOOL			gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");
	BOOL			strict = !strcmp(NXGetDefaultValue([NXApp appName], SSTRICT_COPY), "YES");

	if (mask == NX_DragOperationPrivate || (localSrc && [source isKindOf:[IconView class]]) ||
		(!localSrc &&
		 ((strict && mask != NX_DragOperationCopy) ||
		  (!strict && !(mask & NX_DragOperationCopy)))))	{

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

	if (!draggedView)
		[self createDraggedView:sender];
	newLoc = [self viewLocationForContext:sender];

	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 */
		}
	}

	if (localSrc)
		[source setKeepDraggedIcons:(mask == NX_DragOperationCopy)];
	return (mask == NX_DragOperationCopy) ? NX_DragOperationCopy :
		((localSrc)?NX_DragOperationGeneric:NX_DragOperationCopy);
}


- (int)getHorizLimit:(char *)dragString sep:(char *)sep ctrl:(BOOL)ctrl
{
	char	*ptr;
	int		horizLimit;

	horizLimit = 10000;
	if (strlen(dragString) && !ctrl)	{
		horizLimit = 0;
		ptr = dragString;
		while(*ptr)	{
			if (*ptr == *sep)
				horizLimit++;
			ptr++;
		}
		horizLimit = (int)sqrt((float)horizLimit);
	}
	return horizLimit;
}

/*
 *  Get rid of the resources we used to drag the image around.
 */
- draggingExited:(id <NXDraggingInfo>)sender
{
	BOOL	gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");

	if ([sender draggingSource] != self &&
		!([sender draggingSourceOperationMask] & NX_DragOperationCopy))	{
		if (draggedView)	{
			[self removeView:draggedView];
			[draggedView free];
			draggedView = nil;
		}
		return self;
	}

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

    return self;
}


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


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

	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;
	BOOL	found = NO;
	int		count = [subviews count];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	[window disableFlushWindow];
	for(i = 0, theView = viewListPtr; i < count; i++, theView++)	{
		if (*theView == nil || ![*theView isKindOf:[IconView class]])
			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] flushWindowIfNeeded];

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

- nukeViewsInRect:(NXRect *)theRect
{
	id		*theView;
	id		*killListPtr;
	int		i;
	NXRect	viewRect;
	NXPoint	thePoint;
	int		count = [subviews count];
	id		killList = [[List allocFromZone:[self zone]] init];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");

	[window disableFlushWindow];
	for(i = 0, theView = viewListPtr; i < count; i++, theView++)	{
		if (*theView == nil || ![*theView isKindOf:[IconView class]])
			continue;
		[*theView getFrame:&viewRect];
		thePoint.x = NX_MIDX(&viewRect);
		thePoint.y = NX_MIDY(&viewRect);
		if (NXIntersectsRect(&viewRect, theRect))
			[killList addObject:*theView];
	}
	count = [killList count];
	killListPtr = (count > 0) ? NX_ADDRESS(killList) : (id *)NULL;
	for(i = 0, theView = killListPtr; i < count; i++, theView++)
		[self deleteView:*theView];
	[[window reenableFlushWindow] flushWindowIfNeeded];

	[killList free];
	if (count)	{
		if (useSound)
			[destroySound play:self];
		[[NXApp delegate] updateMenus];
		[self writeShelf];
	}
	return self;
}

- deleteSelectedCells
{
	id		*view;
	int		i;
	id		*cutListPtr = (id *)NULL;
	int		count = [subviews count];
	id		cutList = [[List allocFromZone:[self zone]] init];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	[window disableFlushWindow];
	for(i = 0, view = viewListPtr; i < count; i++, view++)	{
		if ([*view state])
			[cutList addObject:*view];
	}
	count = [cutList count];
	cutListPtr = (count > 0) ? NX_ADDRESS(cutList) : (id *)NULL;
	for(i = 0, view = cutListPtr; i < count; i++, view++)
		[self deleteView:*view];

	[[window reenableFlushWindow] flushWindowIfNeeded];
	[cutList free];
	if (count)	{
		[[NXApp delegate] updateMenus];
		[self writeShelf];
	}

	return self;
}

- mouseDown:(NXEvent *)event
{
	int			selCnt;
	int			savedMask;
	NXPoint		p;
	NXRect		theRect;
	BOOL		started = NO;
	NXPoint		origPt = event->location;
	int			mask = NX_MOUSEDRAGGEDMASK|NX_MOUSEUPMASK;
	BOOL		alt = (event->flags & NX_ALTERNATEMASK) ? YES : NO;
	BOOL		cmd = (event->flags & NX_COMMANDMASK) ? YES : NO;
	BOOL		ctrl = (event->flags & NX_CONTROLMASK) ? YES : NO;
	BOOL		shift = (event->flags & NX_SHIFTMASK) ? YES : NO;

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

	savedMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	if (!shift && !ctrl && (cmd || alt) && ((selCnt = [self hasSelectedCells]) != 0))	{
		NXImage 	*dragImage;
		Pasteboard	*pb = [Pasteboard newName:NXDragPboard];

		if (selCnt == 1)
			dragImage = [IconView getImageForPath:[self selectedCellPath] fileIcon:YES zone:[self zone]];
		else
			dragImage = [[NXImage allocFromZone:[self zone]] initFromSection:"Sheaf"];

		dragSourceView = nil;
		origPt.x -= 32.0; origPt.y -= 32.0;

		keepDraggedIcons = alt;
		[self copy:self toPasteboard:pb andCut:NO];
		[self dragImage:dragImage at:&origPt offset:&frame.origin
		          event:event pasteboard:pb source:self slideBack:YES];
		[dragImage free];

		[self deselectAll:self];
		[window setEventMask:savedMask];
		[[NXApp delegate] updateMenus];
		return self;
	}
	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 (ctrl)
			[self nukeViewsInRect:&theRect];
		else	{
			[[NXApp delegate] deselectDock];
			[self selectViewsInRect:&theRect deselect:!shift];
		}
	}

	[window setEventMask:savedMask];
	[[NXApp delegate] updateMenus];
	return self;
}

- setKeepDraggedIcons:(BOOL)flag
{
	keepDraggedIcons = flag;
	return self;
}

- getNextFrame:(NXRect *)theFrame firstFrame:(NXRect *)firstFrame limit:(float)limit useSlimMargin:(BOOL)flag
{
	int			maxTries;
	NXRect		origFrame = *theFrame;
	BOOL		goingDown = TRUE;

	if (NXContainsRect(&frame, theFrame) &&
		![self isAnyViewAt:theFrame->origin besides:draggedView useSlimMargin:flag])
		return self;

	theFrame->origin = firstFrame->origin;
	maxTries = NX_WIDTH(&frame) * NX_HEIGHT(&frame)/(gridValue * gridValue);
	while (maxTries)	{
		/* scan left */
		do	{
			maxTries--;
			NX_X(theFrame) -= NX_WIDTH(theFrame);
		} while ((NX_X(theFrame) > 0.0) && ((NX_X(firstFrame) - NX_X(theFrame))/NX_WIDTH(theFrame) <= limit) &&
				 ([self isAnyViewAt:theFrame->origin besides:draggedView useSlimMargin:flag]));

		/* if we're within bounds, we're done */
		if (NX_X(theFrame) > 0.0  && ((NX_X(firstFrame) - NX_X(theFrame))/NX_WIDTH(theFrame) <= limit))
			return self;
		else	{
			if (goingDown)	{
				if (NX_Y(theFrame) >= NX_HEIGHT(theFrame))
					NX_Y(theFrame) -= NX_HEIGHT(theFrame);
				else	{
					goingDown = NO;
					NX_Y(theFrame) = NX_MAXY(&origFrame);
				}
			}
			else	{
				if (NX_MAXY(theFrame) <= NX_HEIGHT(&frame))
					NX_Y(theFrame) += NX_HEIGHT(theFrame);
				else	{
					limit = 100.0;
					goingDown = YES;
					NX_Y(theFrame) = NX_Y(firstFrame);
				}
			}
			NX_X(theFrame) = NX_MAXX(firstFrame);
		}
	}

	return self;
}


- concludeDragOperation:(id <NXDraggingInfo>)sender
{
	id			oldImage;
	int			len;
	int			count;
	int			horizLimit;
	int			horizCount;
	char		*ptr;
	char		*path;
	char		*dragString;
	NXRect		newFrame;
	NXRect		viewFrame;
	BOOL		useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");
	BOOL		srcIsDockMgr = [[sender draggingSource] isKindOf:[DockMgrView class]];

	[draggedView getFrame:&viewFrame];
	newFrame = viewFrame;
//	NX_X(&newFrame) = NX_MAXX(&viewFrame);

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

	[window disableFlushWindow];
	path = strtok(dragString, "\t");
	while(path && strlen(path))	{
		oldImage = (!count && [sender isDraggingSourceLocal] && !srcIsDockMgr)? [draggedView image] : nil;
		[self getNextFrame:&newFrame firstFrame:&viewFrame limit:(float)horizLimit useSlimMargin:NO];
		[self createViewForPath:path withImage:oldImage at:&newFrame.origin];
		NXPing();
		path = strtok(NULL, "\t");
	}
	[[window reenableFlushWindow] flushWindowIfNeeded];

	if (useSound && (![sender isDraggingSourceLocal] ||  srcIsDockMgr))
		[fiendSound play];

	if (draggedView != nil)	{
		[self removeView:draggedView];
		[draggedView free];
	}
	[[NXApp delegate] updateMenus];
	[self perform:@selector(writeShelf) with:nil afterDelay:1000.0 cancelPrevious:YES];
    return self;
}

- setDragView:aView onEvent:(NXEvent *) e withOffset:(NXPoint *) offset
   atLocation:(const NXPoint *) location
{
    void		*data;
    int			length;
    NXPoint		myLoc;
//	NXSize		viewSize = {gridValue, gridValue};
    id			pb = [Pasteboard newName:NXDragPboard];
//	NXImage 	*dragImage = [[NXImage allocFromZone:[self zone]] initSize:&viewSize];

    dragSourceView = aView;
    keepDraggedIcons = (e->flags & NX_ALTERNATEMASK) ? YES : NO;

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

/*
	[aView allocateGState];
	[aView lockFocus]; [aView unlockFocus];
	if ([dragImage lockFocus])	{
		PScompositerect(0.0, 0.0, gridValue, gridValue, NX_CLEAR);
		PScomposite(0.0, 0.0, gridValue, gridValue, [aView gState], 0.0, 0.0, NX_COPY);
		[dragImage unlockFocus];
	}
	[aView freeGState];
*/
    myLoc = *location;
    [aView convertPoint:&myLoc toView:self];
    [self dragImage:[aView image] at:&myLoc offset:offset event:e pasteboard:pb
	 source:self slideBack:YES];

//	[dragImage free];
	[self deselectAll:self];
//	[self display];

    return self;
}

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

	if (dragSourceView != nil)	{
		[dragSourceView getFrame:&theFrame];
		[self display:&theFrame :1 :NO];
		if (!keepDraggedIcons)
			[self removeView:dragSourceView];
	}

    return self;
}


- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
     deposited:(BOOL)didDeposit
{
	if (dragSourceView != nil)	{
		if (didDeposit && !keepDraggedIcons)
			[self deleteView:dragSourceView];
		else
			[self addView:dragSourceView];
	}
	else if (didDeposit && !keepDraggedIcons)
		[self deleteSelectedCells];

	[[NXApp delegate] updateMenus];
    return self;
}


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


- (const char *)selectedCellPath
{
	id		*view;
	int		i;
	int		len;
	char	*path = "";
	int		count = [subviews count];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	for(i = 0, view = viewListPtr; i < count; i++, view++)	{
		if ([*view state])	{
			[*view getData:(void *)&path andLength:&len];
			break;
		}
	}
	return (const char *)path;
}

- (int)hasSelectedCells
{
	id		*view;
	int		i;
	int		selCnt = 0;
	int		count = [subviews count];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

	for(i = 0, view = viewListPtr; i < count; i++, view++)	{
		if ([*view state])
			selCnt++;
	}
	return selCnt;
}

- copy:sender toPasteboard:pb andCut:(BOOL)cutFlag
{
	id		*aView;
	id		*cutListPtr;
	int		i;
	int		junk;
	int		length;
	int		maxLength;
	char	*viewPath;
	NXRect	frameRect;
	char	*cutString;
	NXPoint	refPt = {-16000.0, -16000.0};
	int		count = [subviews count];
	id		*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;
	id		cutList = [[List allocFromZone:[self zone]] init];
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");
	NXStream	*theStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	const char	*types[] = {FIEND_PBTYPE, NXFilenamePboardType, NXAsciiPboardType};

	[pb declareTypes:types num:3 owner:nil];

	for(i = 0, aView = viewListPtr; i < count; i++, aView++)	{
		if ([*aView state])	{
			[*aView getFrame:&frameRect];
			refPt.x = (refPt.x > NX_MAXX(&frameRect)) ? refPt.x : NX_MAXX(&frameRect);
			refPt.y = (refPt.y > NX_MAXY(&frameRect)) ? refPt.y : NX_MAXY(&frameRect);
			[cutList addObject:*aView];
		}
	}

	NXPrintf(theStream, "SHELF\t");
	count = [cutList count];
	cutListPtr = (count > 0) ? NX_ADDRESS(cutList) : (id *)NULL;
	for(i = 0, aView = cutListPtr; i < count; i++, aView++)	{
		[*aView getFrame:&frameRect];
		[*aView getData:(void **)&viewPath andLength:&junk];
		NXPrintf(theStream,"%6d%7.2f%7.2f  %s\t", 0,
				 (NX_MAXX(&frameRect) - refPt.x)/gridValue,
				 (NX_MAXY(&frameRect) - refPt.y)/gridValue,
				 viewPath);
	}
	NXGetMemoryBuffer(theStream, &cutString, &length, &maxLength);
	[pb writeType:FIEND_PBTYPE data:cutString length:strlen(cutString)];
	NXClose(theStream);	

	length = 0;
	theStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	for(i = 0, aView = cutListPtr; i < count; i++, aView++)	{
		[*aView getData:(void **)&viewPath andLength:&junk];
		NXPrintf(theStream, "%s\t", viewPath);
	}
	NXGetMemoryBuffer(theStream, &cutString, &length, &maxLength);
	cutString[strlen(cutString)-1] = '\0';
	[pb writeType:NXFilenamePboardType data:cutString length:strlen(cutString)];

	for(i = 0; i < strlen(cutString); i++)	{
		if (cutString[i] == '\t')
			cutString[i] = '\n';
	}
	if (cutString[strlen(cutString)-1] != '\n') strcat(cutString, "\n");
	[pb writeType:NXAsciiPboardType data:cutString length:strlen(cutString)];
	NXClose(theStream);

	if (cutFlag)	{
		if (useSound)
			[destroySound play:self];
		[window disableFlushWindow];
		for(i = 0, aView = cutListPtr; i < count; i++, aView++)
			[self deleteView:*aView];
		[[window reenableFlushWindow] flushWindowIfNeeded];
	}

	[cutList free];
	[[NXApp delegate] updateMenus];
	[self perform:@selector(writeShelf) with:nil afterDelay:1000.0 cancelPrevious:YES];

	return self;
}

- showInsertCursor:(NXPoint *)locus ctrl:(BOOL *)ctrl
{
	NXEvent	*event;

	[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;

	return self;
}

- paste:sender toPasteboard:pb
{
	int		len;
	int		horizLimit;
	int		horizCount;
	int		launchFlag;
	char	*path;
	char	*dragString;
	char	tokenString[500];
	BOOL	ctrl;
	BOOL	fromDock;
	BOOL	fromShelf;
	NXRect 	newFrame;
	NXCoord	x;
	NXCoord	y;
	NXPoint	locus;
	NXPoint	newPoint;
	NXPoint	maxOffset;
	const	char *type;
	char	*sep = "\t";
	NXRect	bogusFrame = {{-1000.0, -1000.0}, {gridValue, gridValue}};
	NXRect	viewFrame = {{0.0, 0.0}, {gridValue, gridValue}};
	BOOL	gridEnabled = !strcmp(NXGetDefaultValue([NXApp appName], GRID_ENABLE), "YES");
	BOOL	useSound = !strcmp(NXGetDefaultValue([NXApp appName], USE_SOUND), "YES");
	const	char *types[] = {FIEND_PBTYPE, NXFilenamePboardType};

	type = [pb findAvailableTypeFrom:types num:2];
	if (type && !strcmp(type, FIEND_PBTYPE))	{
		if (![pb readType:FIEND_PBTYPE data:&dragString length:&len])
			return self;
		sep = "\t";
	}		
	else if (type && !strcmp(type, NXFilenamePboard))	{
		if (![pb readType:NXFilenamePboardType data:&dragString length:&len])
			return self;
		sep = "\t";
	}
/*
	else if (!strcmp(type, NXAsciiPboard))	{
		if (![pb readType:NXAsciiPboardType data:&dragString length:&len])
			return self;
		sep = "\n";
	}
*/
	else	{
		NXBeep();
		return self;
	}

	if (len == 0)	{
		NXBeep();
		return self;
	}

	if (pb != [Pasteboard new])	{
		NXRunAlertPanel([NXApp appName],
						"Click mouse to choose site...",
						"Ok", NULL, NULL);
	}

	[self showInsertCursor:&locus ctrl:&ctrl];
	if (gridEnabled)	{
		locus.x = locus.x - (int)locus.x % gridValue;
		locus.y = locus.y - (int)locus.y % gridValue;
	}

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

	horizCount = 0;
	horizLimit = [self getHorizLimit:dragString sep:sep ctrl:ctrl];
	[window disableFlushWindow];
	path = strtok(dragString, sep);
	fromDock = (!strcmp(path, "DOCK")) ? YES : NO;
	fromShelf = (!strcmp(path, "SHELF")) ? YES : NO;
	if (fromDock)	{
		path = strtok(NULL, sep);
		sscanf(path, "%f %f", &maxOffset.x, &maxOffset.y);
	}
	if (fromDock || fromShelf)
		path = strtok(NULL, sep);
	while(path && strlen(path))	{
		if (!strcmp(type, FIEND_PBTYPE))	{
			sscanf(path, "%d %f %f %[^\n\t]", &launchFlag, &x, &y, tokenString);
			if (fromDock)	{
				x -= maxOffset.x;
				y -= maxOffset.y;
			}
			NX_X(&newFrame) = locus.x + (x - 1.0) * gridValue;
			NX_Y(&newFrame) = locus.y + (y - 1.0) * gridValue;
		}
		else
			strcpy(tokenString, path);
		if (ctrl) newFrame = bogusFrame;
		[self getNextFrame:&newFrame firstFrame:&viewFrame
		             limit:(float)horizLimit useSlimMargin:NO];
		[self createViewForPath:tokenString withImage:nil at:&newFrame.origin];
		NXPing();
		path = strtok(NULL, sep);
	}
	[[window reenableFlushWindow] flushWindowIfNeeded];
	if (useSound)
		[fiendSound play:self];

	[pb deallocatePasteboardData:dragString length:len];
	[[NXApp delegate] updateMenus];
	[self perform:@selector(writeShelf) with:nil afterDelay:1000.0 cancelPrevious:YES];
	return self;
}

- paste:sender
{
	return [self paste:sender toPasteboard:[Pasteboard new]];
}
	
- (FILE *)openShelfFor:(char *)how path:(char *)path
{
	char	*path2 = "";
	FILE	*f;

    sprintf(path, "%s/.AppInfo/Fiend/%s.%.0f.%.0f", NXHomeDirectory(),
			FIENDSHELF_FILE, NX_WIDTH(&frame), NX_HEIGHT(&frame));

	if (*how == 'w')	{
		path2 = (char *)malloc(strlen(path)+2);
		sprintf(path2, "%s~", path);
		rename(path, path2);
	}

	f = fopen(path, how);
	if (*how == 'r')	{
		if (f == (FILE *)NULL)	{
			NXLogError("Couldn't find %s, trying %s~", path, path);
			strcat(path, "~");
			if ((f = fopen(path, how)) == (FILE *)NULL)	{
				sprintf(path, "%s/.AppInfo/Fiend/%s", NXHomeDirectory(), FIENDSHELF_FILE);
				NXLogError("...trying %s", path);
				if ((f = fopen(path, how)) == (FILE *)NULL)	{
					sprintf(path, "%s/.FiendShelf", NXHomeDirectory());
					NXLogError("...trying %s", path);
					if ((f = fopen(path, how)) == (FILE *)NULL)
						NXLogError("No FiendShelf file!!");
				}
			}
		}
		else	{
			fseek(f, 0L, SEEK_END);
			if (ftell(f) <= 5)	{
				NXLogError("%s is empty - trying backup", path);
				fclose(f);
				strcat(path, "~");
				if ((f = fopen(path, how)) != (FILE *)NULL)	{
					fseek(f, 0L, SEEK_END);
					if (ftell(f) <= 5)
						NXLogError("Backup is empty - building empty shelf");
				}
			}
			if (f != (FILE *)NULL)
				rewind(f);
		}
		return f;
	}
	else if (f == (FILE *)NULL)	{
		NXBeep();
		NXRunAlertPanel([NXApp appName],
						"Could not open %s to save Fiend Shelf!!",
						NULL, NULL, NULL, path);
		rename(path2, path);
		free(path2);
		return f;
	}			

	if (*how == 'w') free(path2);
    return f;
}


- closeShelf:(FILE *)file
{
    fclose(file);
    return self;
}


- readShelf:(BOOL)showProgress
{
	float	i;
	float	count;
    FILE	*file;
    char	line[MAXPATHLEN + 30];
    char	*path;
    NXPoint	point;

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

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

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

		sscanf(line, "%f %f", &point.x, &point.y);
		if (strlen(line) > 14) {
			path = line + 14;
			if (rindex(path, '\n') != NULL)
				*rindex(path, '\n') = '\0';
		}
		else
			continue;

		[self createViewForPath:path withImage:nil at:&point];
    }

    [self closeShelf:file];

    return self;
}


- writeShelf
{
	id			*view;
    int			i;
	int			length;
	int			statRet;
	char		*iconPath;
	char		path[MAXPATHLEN];
	char		path2[MAXPATHLEN];
    FILE		*file;
    NXRect		rect;
	struct stat	theStat;
	int			count = [subviews count];
	id			*viewListPtr = (count > 0) ? NX_ADDRESS(subviews) : (id *)NULL;

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

	for(i = 0, view = viewListPtr; i < count; i++, view++)	{
		if (![*view isKindOf:[IconView class]])
			continue;

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

		if (fprintf(file, "%6.0f %6.0f %s\n", rect.origin.x, rect.origin.y, iconPath) < 0)	{
			perror("Fiend 'writeShelf'");
			fclose(file);
			sprintf(path, "%s/.AppInfo/Fiend/%s.%.0f.%.0f", NXHomeDirectory(),
					FIENDSHELF_FILE, NX_WIDTH(&frame), NX_HEIGHT(&frame));
			sprintf(path2, "%s~", path);
			fclose(file);
			unlink(path);
			rename(path, path2);
			return nil;
		}
    }

    [self closeShelf:file];
	sprintf(path2, "%s/.AppInfo/Fiend/FiendShelf", NXHomeDirectory());
	statRet = stat(path2, &theStat);
	if (((statRet != -1) && (theStat.st_mode & S_IFLNK)) || statRet == -1)	{
		unlink(path2);
		symlink(path, path2);
	}
    return self;
}

@end
