
#pragma .h #import <Foundation/NSObject.h>
#pragma .h #import <Foundation/NSArray.h>
#pragma .h #import <Foundation/NSString.h>
#pragma .h #import <AppKit/NSDragging.h>
#pragma .h #import <AppKit/NSImage.h>
#pragma .h 
#pragma .h #import "IKBrowserManager.h"
#pragma .h @class IKAnnouncer;
#pragma .h 

#pragma .h typedef struct _folderflags {
#pragma .h 	int		draggable:1;
#pragma .h 	int		dragAccepting:1;
#pragma .h 	int		editable:1;
#pragma .h 	int		hidden:1;
#pragma .h 	int		leaf:1;
#pragma .h 	int		freeing:1;
#pragma .h } folderFlags;

#import <Foundation/NSArchiver.h>
#import <Foundation/NSUtilities.h>

#import <InterfaceBuilder/InterfaceBuilder.h>

#import "IKAnnouncer.h"
#import "IKfunctions.h"
#import "IKFolder.h"
#import "IKList.h"
#import "IKIconPath.h"
#import "NSObject.LoadImage.h"

@implementation IKFolder : NSObject 
#pragma .h <IKIconObject, IKGraphNode>
#pragma .h {
#pragma .h 	NSString		*name;
#pragma .h 	
#pragma .h 	IKAnnouncer		*users;
#pragma .h 	NSImage			*image,
#pragma .h 					*miniImage,
#pragma .h 					*acceptingDragImage,
#pragma .h 					*miniAcceptingDragImage;
#pragma .h 	id				dragging;
#pragma .h 	NSMutableArray	*parents,
#pragma .h 					*children;
#pragma .h 	
#pragma .h 	int				draggingSession,
#pragma .h 					searchMarker;
#pragma .h 		
#pragma .h 	unsigned int	operationMask;
#pragma .h 	folderFlags		flags;
#pragma .h }

static int		currentMark = 1;

static NSImage	* miniDocument;
static NSImage	* document;
static NSImage	* folder;
static NSImage	* miniFolder;
static NSImage	* openFolder;
static NSImage	* miniOpenFolder;


+ (void)initialize
{
    if (self == [IKFolder  class])
    {
        IKInitIDpboardType();
        document = [[self loadImageFromBundle:@"IKDocument"] retain];
        miniDocument = [[self loadImageFromBundle:@"IKMiniDocument"] retain];
        folder = [[self loadImageFromBundle:@"IKFolder"] retain];
        miniFolder = [[self loadImageFromBundle:@"IKMiniFolder"] retain];
        openFolder = [[self loadImageFromBundle:@"IKOpenFolder"] retain];
        miniOpenFolder = [[self loadImageFromBundle:@"IKMiniOpenFolder"] retain];
    }
}


+ multipleSelectionClass
{
	return [IKList class];
}


- (NSImage *) getIBImage
{
	return folder;
}


- (NSString *) inspectorClassName
{
	return @"IKFolderInspector";
}


- init
{
	return [self  init:@"IconKit"];
}


- init: (NSString *) theName
{
    self = [super  init];
    if( !self ) return nil;

    users = [[IKAnnouncer alloc] initOwner: self];
    searchMarker = 0;
    dragging = nil;
    draggingSession = -1;

    name = [theName retain];
    image = nil;
    miniImage = nil;
    acceptingDragImage = nil;
    miniAcceptingDragImage = nil;
    parents =  [[NSMutableArray alloc] init];
    children = [[NSMutableArray alloc] init];

    flags.editable = YES;
    flags.draggable = YES;
    flags.dragAccepting = YES;
    flags.hidden = NO;
    flags.leaf = NO;
    flags.freeing = NO;
    
    return self;
}


- (IKFolder *)copyWithZone:(NSZone *)zone
{
	IKFolder * copy = NSCopyObject(self, 0, zone);

	if (copy) {
		copy->flags = flags;
		copy->name = name ? [name copy] : nil;
		copy->users = [[IKAnnouncer alloc] initOwner: copy];
		copy->parents = [[NSMutableArray alloc] init];
		copy->children = [children mutableCopy];
		copy->image = image ? [image copy] : nil;
		copy->miniImage = miniImage ? [miniImage copy] : nil;
		copy->acceptingDragImage = acceptingDragImage ? [acceptingDragImage copy] : nil;
		copy->miniAcceptingDragImage = miniAcceptingDragImage ? [miniAcceptingDragImage copy] : nil;

		[copy->children makeObjectsPerform:@selector(addParent:) withObject:copy];
	}
	return copy;
}

- (void)dealloc
{
#if DEBUG
	NSLog (@"freeing folder: %s\n", name ? [name cString] : "");
#endif

    flags.freeing = YES;
    [users announce: @selector(willFree:)];

    // ??? We really don't need that "freeing stuff".. do we ? That was back in the old non retain days.

    [children release];
    [parents release];
    [users release];
    [name release];

    [image release];

    [miniImage release];
    [acceptingDragImage release];
    [miniAcceptingDragImage release];

    [super dealloc];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    char	draggable, dragAccepting, editable, hidden, leaf;

    name = [aDecoder decodeObject];
    if(name)
        [name retain];
    [aDecoder decodeValuesOfObjCTypes:"ccccc", &draggable, &dragAccepting, &editable, &hidden, &leaf];

    flags.draggable = draggable;
    flags.dragAccepting = dragAccepting;
    flags.editable = editable;
    flags.hidden = hidden;
    flags.leaf = leaf;
    flags.freeing = NO;

    image = (NSImage *)[[aDecoder decodeObject] retain];
    miniImage = (NSImage *)[[aDecoder decodeObject] retain];
    acceptingDragImage = (NSImage *)[[aDecoder decodeObject] retain];
    miniAcceptingDragImage = (NSImage *)[[aDecoder decodeObject] retain];

    parents = [[aDecoder decodeObject] retain];
    children = [[aDecoder decodeObject] retain];

    users = [[IKAnnouncer alloc] initOwner: self];

    return self;
}


- (void)encodeWithCoder:(NSCoder *)aCoder;
{
    char draggable = flags.draggable,
         dragAccepting = flags.dragAccepting,
         editable = flags.editable,
         hidden = flags.hidden,
         leaf = flags.leaf;

    [aCoder encodeObject:name];
    [aCoder encodeValuesOfObjCTypes:"ccccc", &draggable, &dragAccepting, &editable, &hidden, &leaf];

    [aCoder encodeObject:image];
    [aCoder encodeObject:miniImage];
    [aCoder encodeObject:acceptingDragImage];
    [aCoder encodeObject:miniAcceptingDragImage];
    [aCoder encodeObject:parents];
    [aCoder encodeObject:children];
}


- (void)addUser:(id)who
{
	[users addUser: who];
}

- (void)addListener:(id)who
{
	[users  addListener: who];
}

- (void)removeListener: who
{
	[users removeListener: who];
}

- (void)removeUser:(id)who
{
	[users removeUser: who];
	[self checkForFree];
}


- (void)checkForFree
{
    // <<HACK>> We really shouyldn't have to deal with that any longer !!

    if(		([users numUsers] == 0)
         &&	([parents count] == 0)
         && [self garbageCollect]
         && (!flags.freeing)
         && (![NSApp conformsToProtocol:@protocol(IB)]))
    {
        [children makeObjectsPerform: @selector(removeParent:) withObject:self];
    }
}

- (BOOL) garbageCollect
{
    // <<HACK>. Should be obsolete by now...

	return YES;
}

- (NSImage *)image
{
	return	image ? image : (flags.leaf ? document : folder);
}

- (NSImage *)miniImage
{
	return	miniImage ? miniImage : (flags.leaf ? miniDocument : miniFolder);
}

- (NSImage *)acceptingDragImage
{
	return	acceptingDragImage ? acceptingDragImage : openFolder;
}

- (NSImage *)miniAcceptingDragImage
{
	return	miniAcceptingDragImage ? miniAcceptingDragImage : miniOpenFolder;
}


- (NSString *)name          {  return name;                   }
- (BOOL) isDraggable        {  return flags.draggable;        }
- (BOOL) isDragAccepting    {  return flags.dragAccepting;    }
- (BOOL) isEditable         {  return flags.editable;         }
- (BOOL) isHidden           {  return flags.hidden;           }
- (BOOL) isLeaf             {  return flags.leaf;             }
- (NSArray *)parents        {  return parents;                }
- (NSArray *)children       {  return children;               }


- setDraggable: (BOOL) flag     {  flags.draggable = flag;      return self;   }
- setDragAccepting: (BOOL) flag {  flags.dragAccepting = flag;	return self;   }
- (void)setEditable:(BOOL)flag  {  flags.editable = flag;                      }
- setHidden: (BOOL) flag        {  flags.hidden = flag;         return self;   }


- (void)setLeaf:(BOOL)flag
{
    if (flags.leaf != flag) 
{
        flags.leaf = flag;
        if( !image ) [users announce:@selector(didChangeImage:)];
        if( !miniImage ) [users announce:@selector(didChangeMiniImage:)];

        [users announce:@selector(didChangeProperties:)];
    }
}

- (void)setName:(NSString *)theName
{
    if( ![name isEqual:theName])
    {
        [name autorelease];
        name = [theName copy];
        [users announce:@selector(didChangeName:)];
    }
}

- (void)setImage:(NSImage *)theImage
{
    if( ![image isEqual:theImage])
    {
        [image autorelease];
        image = [theImage retain];
        [users announce:@selector(didChangeImage:)];
    }
}

- (void)setMiniImage:(NSImage *)theImage
{
    if( ![miniImage isEqual:theImage])
    {
        [miniImage autorelease];
        miniImage = [theImage retain];
        [users announce:@selector(didChangeMiniImage:)];
    }
}

- (void)setAcceptingDragImage:(NSImage *)theImage
{
    if( ![acceptingDragImage isEqual:theImage])
    {
        [acceptingDragImage autorelease];
        acceptingDragImage = [theImage retain];
        [users announce:@selector(didChangeAcceptingDragImage:)];
    }
}

- (void)setMiniAcceptingDragImage:(NSImage *)theImage
{
    if( ![miniAcceptingDragImage isEqual:theImage])
    {
        [miniAcceptingDragImage autorelease];
        miniAcceptingDragImage = [theImage retain];
        [users announce:@selector(didChangeMiniAcceptingDragImage:)];
    }
}

- (NSArray *)pathToNode:(IKFolder *)destination
{
	currentMark++;
	return [self searchFor: destination  via: @selector(children)];
}

- (NSArray *)pathFromNode:(IKFolder *)source
{
	currentMark++;
	return [self searchFor: source  via: @selector(parents)];
}

- (NSMutableArray *)searchFor:(IKFolder *)goal  via: (SEL) getNext
{
    NSMutableArray	*path = nil;

    if (self == goal) {
        path = [[NSMutableArray alloc] init];
    } else if (searchMarker != currentMark) {
        NSArray		*next;
        int			i, n;
        searchMarker = currentMark;
        next = [self performSelector:getNext];
        n = next ? [next count] : 0;

        for (i = 0;  i < n;  i++)
            if ((path = [[next objectAtIndex: i] searchFor:goal via:getNext]) != nil)
                break;
    }
    if(path != nil) {
        [path insertObject:self atIndex:0];
    }
    return [path autorelease];
}

- (void)addChild: child
{
    if( ![children containsObject:child] )
    {
        [child addParent:self];
        [children addObject:child];
        [users announce:@selector(didAddChild:)];
    }
}

- (void)addChildren:(NSArray *)theChildren
{
    int i, n = [children count];

    [users setSendAnnouncements: NO];
    for (i = 0;  i < [theChildren count];  i++)
        [self addChild: [theChildren objectAtIndex: i]];

    [users  setSendAnnouncements: YES];
    if (n != [children count])
        [users announce: @selector(didAddChildren:)];
}


- (void)addParent:(IKFolder *)parent
{
    if( ![parents containsObject:parent] )
    {
        [parents addObject:parent];
        [users announce:@selector(didAddParent:)];
    }
}

- (void)removeChild:(IKFolder *)child withAnnouncement:(BOOL)flag
{
    [child removeParent: self];
    [children removeObject: child];
    if( flag ) [users announce:@selector(didRemoveChild:)];
}

- (void)removeChild:(IKFolder *)child
{
	[self removeChild:child withAnnouncement:YES];
}

- (void)removeChildren:(NSArray *)theChildren withAnnouncement:(BOOL)yesNo
{
    int	i, count = [theChildren count];

    if(yesNo)
        [users  setSendAnnouncements: NO];
    for (i = (count-1);  i >= 0; i--)
        [self removeChild: [theChildren objectAtIndex: i] withAnnouncement:yesNo];
    if(yesNo) {
        [users  setSendAnnouncements: YES];
        [users  announce: @selector(didRemoveChildren:)];
    }
}

- (void)removeChildren:(NSArray *)theChildren
{
    [self removeChildren:theChildren withAnnouncement:YES];
}

- (void)removeParent: parent
{
    [parents removeObject: parent];
    [users announce: @selector(didRemoveParent:)];
    [self checkForFree];
}

- (unsigned int) draggingEntered: (id <NSDraggingInfo>) sender
{
    int n = [sender draggingSequenceNumber];

    if (draggingSession != n)
    {
        draggingSession = n;
        dragging = [[[self class] readFromPasteboard : [sender  draggingPasteboard]] retain];

        operationMask =	(!dragging || (dragging == self))
            ? NSDragOperationNone
            : ( ![[self children] containsObject:dragging])
                ? NSDragOperationCopy
                : (([[sender draggingSource] class] == [IKIconPath class]) || [[dragging parents] count]  <= 1)
                    ? NSDragOperationAll
                    : NSDragOperationLink | NSDragOperationCopy;
    }
    return [self draggingOperation: sender];
}


- (unsigned int) draggingUpdated: (id <NSDraggingInfo>) sender
{
    return [self draggingOperation: sender];
}


- (unsigned int) draggingOperation: (id <NSDraggingInfo>) sender
{
    unsigned int
    choices = operationMask & [sender draggingSourceOperationMask] & ~NSDragOperationPrivate;
    while (choices & (choices - 1))
        choices &= choices - 1;
    return choices;
}


/* ========================================================================== */


- (BOOL) performDragOperation: (id <NSDraggingInfo>) sender
{
    unsigned int   	op = [self  draggingOperation: sender];
    id             		iconPath;
    IKFolder       *	parent = nil;
    NSMutableArray *	original = [[NSMutableArray alloc] init],
                   *	final = original;
    int            		i;
    BOOL           		multiple = [dragging isKindOfClass: [[self class] multipleSelectionClass]];
    BOOL				moving = op & NSDragOperationGeneric;
    BOOL				copying = !moving && (op & NSDragOperationCopy);
	
    if (multiple)
        [original addObjectsFromArray:dragging];
    else
        [original addObject:dragging];

    if (moving)	{
        int n = [[dragging parents] count];
        if(n == 0) {
            parent = nil;
            } else if(n == 1) {
                parent = [[dragging parents] objectAtIndex: 0];
            } else {
                iconPath = [sender draggingSource];
                parent = [[iconPath cellAtRow:0 column:[iconPath selectedColumn] - 1] delegate];
            }
    }

    if (copying) {

        // <<HACK>. We propably should use the MiscDeepCopy here..

        final = [original mutableCopy];
        for (i = 0;  i < [final count]; i++)
            [final replaceObjectAtIndex:i withObject:[[final objectAtIndex:i] copy]];
    }
    [self addChildren:final];

    if ((parent != nil) && (parent != self))
        [parent removeChildren:original];
    [original release];
    if (copying)
        [final release];
	
    return YES;
}

+ (NSArray *)pasteTypes
{
	return IKIDPasteTypes();
}

- (void)copyToPasteboard: (NSPasteboard *) pboard
{
    // <<HACK>> We will do a autorelease fake thing here...since we are currently puting our ID on the board.
    // This at least ensures that we will remain valid until the next main event...but it is definitly unclean !!

    [[self retain] autorelease];
	IKCopyID (pboard, self);
}


+ (id)readFromPasteboard: (NSPasteboard *) pboard
{
    id	object = IKReadID (pboard);

    // <<HACK>> We will do a autorelease fake thing here...since we are currently reading some ID from the board.
    // This at least ensures that we will remain valid until the next main event...but it is definitly unclean !!

    if( [object isKindOfClass:[self class]] ||
        [object isKindOfClass:[self multipleSelectionClass]] )
        return [[object retain] autorelease];
    else
        return nil;
}

- (NSString *)description
{
    return [NSString stringWithFormat:@"%@ (%@)", [super description], [self name]];
}

@end
