// IconWell.m
//
// Free software created 1 Feb 1992
// by Paul Burchard <burchard@math.utah.edu>.

#import "IconWell.h"
#import "IconWellControl.h"
#import <appkit/appkit.h>
#import <objc/Storage.h>


@implementation IconWell

static id windowList;
static id wellsByWindow;

+ initialize
{
    if(self == [IconWell class])
    {
	windowList = [[List alloc] initCount:0];
	wellsByWindow = [[List alloc] initCount:0];
    }
    return self;
}

+ wellListFor:aWindow
{
    int index;
    
    index = [windowList indexOf:aWindow];
    if(index == NX_NOT_IN_LIST) return nil;
    return [wellsByWindow objectAt:index];
}

- initFrame:(const NXRect *)frameRect
{
    id theCell;
    
    [super initFrame:frameRect];
    
    theCell = [[ActionCell alloc] initIconCell:blankIconName];
    [theCell setBezeled:YES];
    [self setCell:theCell];
    
    iconPath = [[Storage alloc] initCount:0 elementSize:sizeof(char) description:"c"];
    sprintf((char *)iconName, "IconWell-%ld", (long)[self self]);

    isHoldOnDrag = YES;
    [self setDraggable:YES droppable:YES];
    return self;
}

- free
{
    int index;
    id list;
    
    [iconPath free];
    if(list = [IconWell wellListFor:window])
    {
    	[list removeObject:self];
	if([list count] <= 0)
	{
	    index = [wellsByWindow indexOf:list];
	    [wellsByWindow removeObjectAt:index];
	    [windowList removeObjectAt:index];
	    [list free];
	}
    }
    return [super free];
}

- windowChanged:newWindow
{
    id list;
    
    // Enter new well into global list.
    if(window || !newWindow) return nil;//!!!
    if(!(list = [IconWell wellListFor:newWindow]))
    {
    	list = [[List alloc] initCount:0];
	[windowList addObject:newWindow];
	[wellsByWindow addObject:list];
    }
    [list addObjectIfAbsent:self];
    return self;
}

- setBezeled:(BOOL)flag
{
    return [cell setBezeled:flag];
}

- (BOOL)isBezeled
{
    return [cell isBezeled];
}

- setBordered:(BOOL)flag
{
    return [cell setBordered:flag];
}

- (BOOL)isBordered
{
    return [cell isBordered];
}

- setDraggable:(BOOL)dragFlag droppable:(BOOL)dropFlag
{
    isDraggable = dragFlag;
    isDroppable = dropFlag;
    return self;
}

- (BOOL)isDraggable
{
    return isDraggable;
}

- (BOOL)isDroppable
{
    return isDroppable;
}

- setHoldOnDrag:(BOOL)flag
{
    if(isDragging) return nil;
    isHoldOnDrag = flag;
    return self;
}

- (BOOL)isHoldOnDrag
{
    return isHoldOnDrag;
}


- clear:sender
{
    // This does not de-alloc Blank.tiff since it was not renamed.
    // This only affects this IconWell because of the unique iconName.
    [cell setIcon:blankIconName];
    [self update]; //!!! make sure image really replaced before freeing
    [[NXImage findImageNamed:(const char *)iconName] free];
    [iconPath setNumSlots:0];
    return self;
}

- (const char *)stringValue
{
    if([iconPath count] <= 0) return 0;
    return (const char *)[iconPath elementAt:0];
}

- getTiffForPath:(const char *)pathString
{
    int ok, length;
    char *tiff, *fakePath, *q;
    const char *p;
    NXStream *imageStream;
    id iconImage;
    
    // Ask WorkSpace for correct TIFF corresponding to path list pathString.
    // Since ``full paths'' are required, prepend '/' to each name if missing.
    if(!pathString) return nil;
    if(!(fakePath = (char *)malloc((strlen(pathString)+1)*sizeof(char))))
    	return nil;
    for(p=pathString, q=fakePath; *p; p++)
    {
        if(p==pathString && *p!='/') *q++ = '/';
	*q++ = *p;
    	if(*p=='\t' && *(p+1)!='/') *q++ = '/';
    }
    *q = 0;
    [[NXApp appSpeaker] setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
    [[NXApp appSpeaker] getFileIconFor:fakePath
    	TIFF:&tiff TIFFLength:&length ok:&ok];
    free(fakePath);

    // If no icon, use generic ".txt" icon.
    if(!ok)
    {
	[[NXApp appSpeaker]
	    setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
	[[NXApp appSpeaker] getFileIconFor:"/file.txt"
	    TIFF:&tiff TIFFLength:&length ok:&ok];
	if(!ok) return nil;
    }
    
    // Create NXImage from TIFF data.
    imageStream = NXOpenMemory(tiff, length, NX_READONLY);
    if(!imageStream) return nil;
    iconImage = [[NXImage alloc] initFromStream:imageStream];
    NXClose(imageStream);
    return iconImage;
}

- setStringValue:(const char *)aString
{
    id iconImage;
    
    // If path is NULL, clear well.
    if(!aString) [self clear:self];
    
    // Ask WorkSpace for correct TIFF corresponding to path aString.
    if(!(iconImage = [self getTiffForPath:aString])) return nil;

    // Enter NXImage into cell, freeing previous image.
    // (Note: Common blank image is not freed as it was never renamed.)
    [cell setIcon:blankIconName];
    [self update]; //!!! make sure image really replaced before freeing
    [[NXImage findImageNamed:(const char *)iconName] free];
    [iconImage setName:(const char *)iconName];
    [cell setIcon:(const char *)iconName];
    
    // Enter new path into iconPath, notify target.
    [iconPath setNumSlots:(strlen(aString)+1)];
    strcpy((char *)[iconPath elementAt:0], aString);
    [self sendAction:[cell action] to:[cell target]];
    return self;
}

- takeStringValueFrom:sender
{
    id oldTarget = nil;
    id rtn;
    
    // If sender is target, don't send action (to avoid circularity).
    if(sender == [self target])
    	{ oldTarget = [self target]; [self setTarget:nil]; }
    rtn = [self setStringValue:[sender stringValue]];
    if(oldTarget) [self setTarget:oldTarget];
    return rtn;
}


- (BOOL)acceptsFirstMouse
{
    return YES;
}

- (int)openFile:(const char *)fullPath ok:(int *)flag
{
    int rtn;
    
    // Ask WorkSpace to open the file.  No fudging paths now.
    [[NXApp appSpeaker] setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
    rtn = [[NXApp appSpeaker] openFile:fullPath ok:flag];
    if(rtn != 0) return(rtn);
    return 0;
}

- (int)prepFile:(const char *)fullPath ok:(int *)flag
{
    // Default is no prep needed on drag-out.
    return 0;
}

- setDelegate:anObject
{
    delegate = anObject;
    return self;
}

- delegate
{
    return delegate;
}

- mouseDown:(NXEvent *)theEvent
{
    int ok;
    char *fileName, *nxt;
    NXPoint mousePoint;
    NXRect iconRect;
    id success, handler;

    if(theEvent->data.mouse.click < 2)
    {
    	// Single click means drag icon out.

	// Check if mouse actually clicked on icon.
	mousePoint = theEvent->location;
	[super convertPoint:&mousePoint fromView:nil];
	[self getBounds:&iconRect];
	[cell getIconRect:&iconRect];
	NX_WIDTH(&iconRect) = NX_HEIGHT(&iconRect) = 48.0;
	if(!NXMouseInRect(&mousePoint, &iconRect, [self isFlipped]))
	    return self;
	
	// Don't actually start the drag until mouse gets dragged a bit.
	if(isDraggable)
	{
	    isDragging = YES;
	    [window addToEventMask:NX_LMOUSEDRAGGEDMASK];
	    dragFromEvent = *theEvent;
	}
	return self;
    }
    else if(theEvent->data.mouse.click == 2)
    {
    	// Double-click means open file(s).
	
	// Delegate this task if possible.
	if([delegate respondsTo:@selector(openFile:ok:)]) handler = delegate;
	else handler = self;
	
	// Open files one by one.
	if([iconPath count] <= 0) return nil;
	fileName = (char *)[iconPath elementAt:0];
	nxt = strchr(fileName, '\t');
	success = self;
	while(fileName)
	{
    	    if(nxt) *nxt = 0;
	    [handler openFile:fileName ok:&ok];
	    if(!ok) success = nil;
	    if(nxt)
	    {
		*nxt = '\t';
	    	fileName = nxt+1;
		nxt = strchr(fileName, '\t');
	    }
	    else fileName = nxt = 0;
	}
	return success;
    }
    else
    {
	// Ignore higher multi-clicks.
	return self;
    }
}

- mouseDragged:(NXEvent *)theEvent
{
    NXRect iconRect;
    int ok;
    char *fileName, *nxt;
    id success, handler;

    // Check if valid drag.
    if(!(isDragging && isDraggable && [cell icon] && [iconPath count]>0))
	{ isDragging = NO; return self; }
	
    // Prep file(s) for drag; delegate this task if possible.
    if([delegate respondsTo:@selector(prepFile:ok:)]) handler = delegate;
    else handler = self;
    if([iconPath count] <= 0) return nil;
    fileName = (char *)[iconPath elementAt:0];
    nxt = strchr(fileName, '\t');
    success = self;
    while(fileName)
    {
	if(nxt) *nxt = 0;
	[handler prepFile:fileName ok:&ok];
	if(!ok) success = nil;
	if(nxt)
	{
	    *nxt = '\t';
	    fileName = nxt+1;
	    nxt = strchr(fileName, '\t');
	}
	else fileName = nxt = 0;
    }
    if(!success) { isDragging = NO; return success; }

    // Try to drag icon out.
    [self getBounds:&iconRect];
    [cell getIconRect:&iconRect];
    NX_WIDTH(&iconRect) = NX_HEIGHT(&iconRect) = 48.0;
    success = [super dragFile:(const char *)[iconPath elementAt:0] 
	fromRect:&iconRect slideBack:YES event:&dragFromEvent];
    isDragging = NO;

    // Take care of no-hold-on-drag.
    if(!isHoldOnDrag)
    {
	// Successful drag: clear path and icon to initial state.
	if(success) [self clear:self];
	// Unsuccessful drag: redisplay old icon in case it left.
	else [cell setIcon:(const char *)iconName];
    }
    return success;
}

- mouseUp:(NXEvent *)theEvent
{
    isDragging = NO;
    return self;
}

- (BOOL)isScreenPointInView:(double)x :(double)y
{
    NXPoint point;
    NXRect rect;

    // If (x,y) is not in this IconWell, try next one in chain.
    point.x = x; point.y = y;
    [window convertScreenToBase:&point];
    [self convertPoint:&point fromView:nil];
    [self getBounds:&rect];
    if(NXMouseInRect(&point, &rect, [self isFlipped])) return YES;
    else return NO;
}

- (int)iconEntered:(int)windowNum at:(double)x :(double)y
    iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
    iconWidth:(double)iconWidth iconHeight:(double)iconHeight
    pathList:(char *)pathList
{
    // Temporarily display new image if (x,y) is in this IconWell.
    if(!isDroppable || ![self isScreenPointInView:x :y])
    	{ isDropping = NO; return 0; }
    [cell setIcon:blankIconName];
    [cell setIcon:newIconName];
    isDropping = YES; return 0;
}

- (int)iconMovedTo:(double)x :(double)y
{
    // Check if (x,y) moved in or out of this IconWell.
    if(!isDroppable) return 0;
    else if(![self isScreenPointInView:x :y])
    {
    	if(!isDropping) return 0;

	// Mouse left; restore old image (unless no-hold-on-drag thing).
	[cell setIcon:blankIconName];
	if(!isDragging || isHoldOnDrag) [cell setIcon:(const char *)iconName];
	isDropping = NO; return 0;
    }
    else if(!isDropping)
    {
    	// Mouse entered; display new image.
	[cell setIcon:blankIconName];
	[cell setIcon:newIconName];
	isDropping = YES; return 0;
    }
    return 0;
}

- (int)iconExitedAt:(double)x :(double)y
{
    if(!isDroppable || !isDropping) return 0;

    // Mouse left; restore old image (unless no-hold-on-drag thing).
    [cell setIcon:blankIconName];
    if(!isDragging || isHoldOnDrag) [cell setIcon:(const char *)iconName];
    isDropping = NO; return 0;
}

- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
{
    const char *path;
    
    // Check if (x,y) moved in or out of this IconWell.
    *flag = 0;
    if(!isDroppable) return 0;
    else if(![self isScreenPointInView:x :y]
    	|| !(path = [[IconWellControl controlFor:window] newIconPath]))
    {
    	if(!isDropping) return 0;

	// Mouse left; restore old image (unless no-hold-on-drag thing).
	[cell setIcon:blankIconName];
	if(!isDragging || isHoldOnDrag) [cell setIcon:(const char *)iconName];
	isDropping = NO; return 0;
    }
    else if(!isDropping)
    {
    	// Mouse entered; display new image.
	[cell setIcon:blankIconName];
	[cell setIcon:newIconName];
    }

    // Replace path and icon with new ones; notify target.
    [iconPath setNumSlots:(strlen(path)+1)];
    strcpy((char *)[iconPath elementAt:0], path);
    [cell setIcon:blankIconName];
    [self update]; //!!! make sure image really replaced before freeing
    [[NXImage findImageNamed:(const char *)iconName] free];
    [[NXImage findImageNamed:newIconName] setName:(const char *)iconName];
    [cell setIcon:(const char *)iconName];
    [self sendAction:[cell action] to:[cell target]];

    // Accept icon and end drop.
    isDropping = NO;
    *flag = 1;
    return 0;
}

@end
