
#pragma .h #import <AppKit/NSActionCell.h>
#pragma .h #import <AppKit/NSImage.h>
#pragma .h #import <AppKit/NSCStringText.h>
#pragma .h
#pragma .h #import "IKIconObject.h"
#pragma .h #import "IKDependency.h"
#pragma .h 
#pragma .h enum {
#pragma .h 	IK_NOPART,
#pragma .h 	IK_TITLEPART,
#pragma .h 	IK_ICONPART
#pragma .h };
#pragma .h 
#pragma .h enum {
#pragma .h 	IK_NOSHELF,
#pragma .h 	IK_UNLOCKED,
#pragma .h 	IK_LOCKED,
#pragma .h 	IK_REALLYLOCKED
#pragma .h };
#pragma .h 
#pragma .h typedef struct _cellflags {
#pragma .h 		unsigned int	showMiniImage:1;
#pragma .h 		unsigned int	showBranch:1;
#pragma .h 		unsigned int	draggable:1;
#pragma .h 		unsigned int	dragAccepting:1;
#pragma .h 		unsigned int	editable:1;
#pragma .h 		unsigned int	container:1;
#pragma .h 		unsigned int	locked:1;
#pragma .h 		unsigned int	reallyLocked:1;
#pragma .h 		unsigned int	multipleLines:1;
#pragma .h 		unsigned int	ghosted:1;
#pragma .h } cellFlags;

#import <AppKit/AppKit.h>
#import "iconkit.h"
#import "IKCellPS.h"

@implementation IKCell : NSActionCell
{
	id <IKIconObject, IKDependency> delegate;
	
	id			image;
	id			miniImage;
	cellFlags	flags;
}

#define DX				9
#define DY				2
#define OFFSET			0
#define GAP				0
#define INSET			4
#define HYSTERESIS		4


static NSTextFieldCell *text;

static id	ghostImage = nil,
			ghostHighlightMask = nil,
			ghosting = nil;

static void initGhostImages (NSSize);

+ (void)initialize
{
    if (self == [IKCell  class]) {
		[self setVersion:2];
		text = [[NSTextFieldCell alloc] initTextCell:@""];
	}
	return;
}


- init
{
	if ((self = [super initTextCell:@""]) != nil) {
		image = nil;
		miniImage = nil;
		delegate = nil;
		
		flags.showMiniImage = NO;
		flags.showBranch = NO;
		flags.draggable = YES;
		flags.dragAccepting = YES;
		flags.editable = YES;
		flags.container = NO;
		flags.locked = YES;
		flags.reallyLocked = YES;
		flags.multipleLines = NO;
		flags.ghosted = NO;
		
		[self setAlignment:NSCenterTextAlignment];
	}
	return self;
}


- initTextCell:(NSString *)theTitle
{
    [self init];
    [self setTitle:theTitle];
    return self;
}


- initImageCell:(NSImage *)anImage
{
    [self init];
    [self setImage:anImage];
    return self;
}


- initImage:(NSImage *) theImage title:(NSString *)theTitle
{
    [self init];
    [self setImage:theImage];
    [self setTitle:theTitle];
    return self;
}


- initDelegate:(id <IKIconObject, IKDependency>) theDelegate
{
    [self init];
    [self setDelegate:theDelegate];
    return self;
}


- initFromCopy:(IKCell *) copy
{
    [self init];
    [self setBranch:[copy isBranch]];
    [self setDraggable:[copy isDraggable]];
    [self setDragAccepting:[copy isDragAccepting]];
    [self setEditable:[copy isEditable]];
    [self setContainer:[copy isContainer]];
    [self setLocked:[copy isLocked]];
    [self setReallyLocked:[copy isReallyLocked]];
    [self setMultipleLines:[copy isMultipleLines]];
    [self setGhosted:[copy isGhosted]];
    [self setTitle:[copy title]];
    [self setImage:[copy image]];
    [self setMiniImage:[copy miniImage]];
    return self;
}


- (void)dealloc
{
    [delegate  removeUser:self];
    return [super dealloc]; self = self;
}


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


- (id)initWithCoder:(NSCoder *)aDecoder
{
	char	showBranch, draggable, dragAccepting,
			editable, container, locked, reallyLocked, multipleLines,
			showMiniImage;
	
    [super initWithCoder:aDecoder];
	
	switch ([aDecoder versionForClassName:@"IKCell"])
	{
		case 2:
			[aDecoder decodeValuesOfObjCTypes:"c", &showMiniImage];	// fall through
			flags.showMiniImage = showMiniImage;		// wenn man es so macht, muessen die neuen ivars in write:
			miniImage = [[aDecoder decodeObject] retain];			// zuerst weggeschrieben werden !
		case 1:
			image = [[aDecoder decodeObject] retain];
			[aDecoder decodeValuesOfObjCTypes:"cccccccc", &showBranch, &draggable,
					&dragAccepting, &editable, &container, &locked,
					&reallyLocked, &multipleLines];

			miniImage = nil;
			flags.showMiniImage = NO;
			flags.multipleLines = multipleLines;
			flags.ghosted = NO;
			break;
			
		case 0:
			image = [[aDecoder decodeObject] retain];
			[aDecoder decodeValuesOfObjCTypes:"ccccccc", &showBranch, &draggable,
					&dragAccepting, &editable, &container, &locked,
					&reallyLocked];
			
			miniImage = nil;
			flags.showMiniImage = NO;
			flags.multipleLines = NO;
			flags.ghosted = NO;
			break;
	}
	flags.showBranch = showBranch;
	flags.draggable = draggable;
	flags.dragAccepting = dragAccepting;
	flags.editable = editable;
	flags.container = container;
	flags.locked = locked;
	flags.reallyLocked = reallyLocked;
	
	return self;
}


- (void)encodeWithCoder:(NSCoder *)aCoder
{
    char	showBranch = flags.showBranch,
                draggable = flags.draggable,
                dragAccepting = flags.dragAccepting,
                editable = flags.editable,
                container = flags.container,
                locked = flags.locked,
                reallyLocked = flags.reallyLocked,
                multipleLines = flags.multipleLines,
                showMiniImage = flags.showMiniImage;

    [super encodeWithCoder:aCoder];
	
    [aCoder encodeValuesOfObjCTypes:"c", &showMiniImage];
    [aCoder encodeObject:miniImage];
    [aCoder encodeObject:image];
    [aCoder encodeValuesOfObjCTypes:"cccccccc", &showBranch, &draggable, &dragAccepting,
        &editable, &container, &locked, &reallyLocked, &multipleLines];
}


- delegate			{		return delegate;						}
- (NSImage *)image		{		return image;							}
- (NSImage *)miniImage		{		return miniImage;						}
- (NSString *)title		{		return [self stringValue];						}
- (BOOL) isShowMiniImage	{		return flags.showMiniImage;				}
- (BOOL) isBranch		{		return flags.showBranch;				}
- (BOOL) isContainer		{		return flags.container;					}
- (BOOL) isLocked		{		return flags.locked;					}
- (BOOL) isReallyLocked		{		return flags.reallyLocked;				}
- (BOOL) isEmptyContainer	{		return flags.container && !delegate;	}
- (BOOL) isMultipleLines	{		return flags.multipleLines;				}
- (BOOL) isGhosted		{		return flags.ghosted;					}


- (BOOL) isDraggable
{
    return flags.draggable && (!delegate || [delegate  isDraggable]);
}


- (BOOL) isDragAccepting
{
    return flags.dragAccepting && (!delegate || [delegate  isDragAccepting]);
}


- (BOOL) isEditable
{
    return flags.editable && (!delegate || [delegate  isEditable]);
}


- (void)setImage:(NSImage *)theImage
{
    image = theImage;
}


- (void)setMiniImage:(NSImage *) theImage
{
    miniImage = theImage;
}

- (void)setTitle:(NSString *)theTitle
{
    NSRect	oldFrame, newFrame;
    id		editor = [self editor];

    [self setStringValue:theTitle ? theTitle : @""];
    if (editor) {
        if (theTitle == nil) {
            [(IKIconPath *)[self controlView] endEditing];
        } else {
            oldFrame = [editor frame];
            [editor setString:[self stringValue]];

//            if ([editor isHorizontallyResizable] || [editor isVerticallyResizable]) {
//                NSRect	titleRect;
//                titleRect = [self titleRectForBounds:[self frame]];
//                [editor setFrame:titleRect];
//            }
            newFrame = [editor frame];
            [[self controlView] displayRect:NSUnionRect(newFrame , oldFrame)];
        }
    }
}


- setShowMiniImage:(BOOL)flag	{  flags.showMiniImage = flag;	return self;   }
- setBranch:(BOOL)flag          {  flags.showBranch = flag;	return self;   }
- setDraggable:(BOOL)flag       {  flags.draggable = flag;      return self;   }
- setDragAccepting:(BOOL)flag	{  flags.dragAccepting = flag;	return self;   }
- (void)setEditable:(BOOL)flag; {  flags.editable = flag;                      }
- setContainer:(BOOL)flag       {  flags.container = flag;      return self;   }
- setLocked:(BOOL)flag          {  flags.locked = flag;		return self;   }
- setReallyLocked:(BOOL)flag	{  flags.reallyLocked = flag;	return self;   }
- setMultipleLines:(BOOL)flag	{  flags.multipleLines = flag;	return self;   }
- setGhosted:(BOOL)flag         {  flags.ghosted = flag;        return self;   }


- (void)setDelegate:(id)theDelegate
{
    id old = delegate;

    delegate = IKCheckConformance (theDelegate) ? theDelegate :nil;
    if(delegate) {
        [delegate  addUser:self];
        [self setMiniImage:[delegate miniImage]];
        [self setImage:[delegate image]];
        [self setTitle:[delegate name]];
    } else {
        [self setMiniImage:nil];
        [self setImage:nil];
        [self setTitle:nil];
    }
    if(old) {
        [old removeUser:self];
    }
}


- willFree:who
{
	if (who == delegate) {
		delegate = nil;
		[self setDelegate:nil];
		[(NSMatrix *)[self controlView] updateCell:self];
	}
	return self;
}


- (int) shelfMode
{
	if (!flags.container)		return IK_NOSHELF;			else
	if (flags.reallyLocked)		return IK_REALLYLOCKED;		else
	if (flags.locked)			return IK_LOCKED;			else
								return IK_UNLOCKED;
}


- setShelfMode:(int) mode
{
	switch (mode) {
		case IK_NOSHELF:
				[self		setContainer:NO];
				break;
		
		case IK_UNLOCKED:
				[self		setContainer:YES];
				[self		setLocked:NO];
				[self		setReallyLocked:NO];
				break;
				
		case IK_LOCKED:
				[self		setContainer:YES];
				[self		setLocked:YES];
				[self		setReallyLocked:NO];
				break;
		
		case IK_REALLYLOCKED:
				[self		setContainer:YES];
				[self		setLocked:YES];
				[self		setReallyLocked:YES];
				break;
	}	
	return self;
}


- didChangeName:sender
{
    if (sender == delegate)
        [self  setTitle:[delegate name]];
    return self;
}


- didChangeImage:sender
{
    if (sender == delegate) {
        [self  setImage:[delegate image]];
        [(NSMatrix *)[self controlView] updateCellInside:self];
    }
    return self;
}


- didChangeMiniImage:sender
{
    if (sender == delegate) {
        [self setMiniImage:[delegate miniImage]];
        [(NSMatrix *)[self controlView] updateCellInside:self];
    }
    return self;
}


- (void)highlight:(BOOL)flag withFrame:(NSRect)cellFrame inView:(NSView *)view
{
    id	anImage = (flags.showMiniImage ? miniImage :image);
    if (anImage && ([self isHighlighted] != flag)) {
        [super highlight:flag withFrame:cellFrame inView:view];
    }
}


- (NSImage *)_imageToDraw
{
    NSImage *anImage = (flags.showMiniImage ? miniImage :image);

    if (flags.ghosted  && anImage != ghosting) {
        NSPoint	origin = { 0.0, 0.0 };
        NSSize	size = [anImage size];
        initGhostImages (size);

        [ghostImage lockFocus];
        [anImage compositeToPoint:origin operation:NSCompositeCopy];
        [ghostHighlightMask compositeToPoint:origin operation:NSCompositeSourceAtop];
        [ghostImage unlockFocus];
        ghosting = anImage;
    }
    return flags.ghosted  ? ghostImage : anImage;
}


static id controlView = nil;
float background;


- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)view
{
	id	anImage = (flags.showMiniImage ? miniImage : image);

	controlView = view;
	if (anImage != NULL)
		[self  drawIcon:cellFrame];
	if ([self stringValue] != NULL)
		[self  drawTitle:cellFrame];
	if (flags.showBranch)
		[self  drawBranch:cellFrame];
}


- (void)drawIcon:(NSRect) iconRect
{
    iconRect = [self imageRectForBounds:iconRect];
    PSsetgray (([self state] | [self isHighlighted])  ? NSWhite : NSLightGray);

    PSiconBackdrop( iconRect.origin.x - DX,
                    iconRect.origin.y - DY,
                    iconRect.size.width + 2.0 * DX,
                    iconRect.size.height + 2.0 * DY
                    );
    iconRect.origin.y += iconRect.size.height;
    [[self _imageToDraw] compositeToPoint:iconRect.origin operation:NSCompositeSourceOver];
}


- (void)drawTitle:(NSRect) titleRect
{
    if ([self editor] == nil) {
        titleRect = [self titleRectForBounds:titleRect];
        [text setStringValue:[self stringValue]];
        [text setAlignment:[self alignment]];
        [text setCellAttribute:NSCellHighlighted to:0];
        [text setFont:[self font]];
        [text setTextColor:[NSColor colorWithCalibratedWhite:flags.ghosted  ?  NSDarkGray : NSBlack alpha:1.0]];
        [text setBackgroundColor:[NSColor lightGrayColor]];
        [text setWraps:[self wraps]];
        if (!flags.multipleLines)
            IKShortenTitle (text, titleRect.size.width);

        [text drawInteriorWithFrame:titleRect inView:controlView];
    }	
}


- (void)drawBranch:(NSRect) cellFrame
{
    id      branchIcon = [NSBrowserCell branchImage];
    NSPoint	origin = cellFrame.origin;
    NSSize	size;

    size = [branchIcon size];
    origin.x += cellFrame.size.width - size.width;
    origin.y += (cellFrame.size.height + size.height) / 2.0;
    [branchIcon compositeToPoint:origin operation:NSCompositeSourceOver];
}


- (NSSize)cellSizeForBounds:(NSRect)frame
{
    NSSize size;
    NSSize imageSize;
    id     anImage = (flags.showMiniImage ? miniImage : image);
    int    alignment = [self alignment];

    size = [super cellSizeForBounds:frame];
    imageSize = [anImage size];

    if(alignment == NSCenterTextAlignment) {
        if ([[self stringValue] length] == 0)
            size.height = 0;
        size.width = MAX (size.width, imageSize.width) + 1 * DX + 1;
        size.height = size.height + imageSize.height + GAP + OFFSET + 1 * DY + 1;
    } else if(alignment == NSLeftTextAlignment) {
        size.width = size.width + imageSize.width + 1 * DX + 1;
        size.height = MAX (size.height, imageSize.height) + GAP + OFFSET + 1 * DY + 1;
    }
    return size;
}


- _getIconRect:(NSRect *) iconRect  titleRect:(NSRect *) titleRect
{
	NSRect	bigRect = {{ 0.0,  0.0 },  { 10000.0, 10000.0 }};
	NSSize	title = { 0.0,  0.0 },
			icon  = { 0.0,  0.0 };
	id		anImage = (flags.showMiniImage ? miniImage : image);
	int		alignment = [self alignment];

        icon = [anImage size];
	if(alignment == NSCenterTextAlignment) {
            iconRect->origin.y += flags.multipleLines ? INSET : (int)(iconRect->size.height - icon.height) / 2;
            iconRect->origin.x += /* GAP + DX + */ (int)(iconRect->size.width - icon.width) / 2;
	} else if(alignment == NSLeftTextAlignment) {
            iconRect->origin.y += (int)(iconRect->size.height - icon.height) / 2;
            iconRect->origin.x += GAP + DX;
	}
        if ([[self stringValue] length] > 0) {
            NSRect aRect, frame = *titleRect;
            if (flags.multipleLines) {
                if(alignment == NSCenterTextAlignment) {
                    titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY;
                    /* titleRect->origin.x += GAP + DX */;
                } else if(alignment == NSLeftTextAlignment) {
                    titleRect->origin.y = iconRect->origin.y;
                    titleRect->origin.x += icon.width + GAP + 2 * DX;
                }
                aRect = *titleRect;
                aRect = NSIntersectionRect(frame , aRect);
                title = aRect.size;
            } else {
                title = [super cellSizeForBounds:bigRect];

                if(alignment == NSCenterTextAlignment) {
                    title.width = MIN (titleRect->size.width, title.width);
                    iconRect->origin.y += OFFSET - (int)(title.height + GAP) / 2;
                    titleRect->origin.y = iconRect->origin.y + icon.height + GAP + DY;
                    titleRect->origin.x += /* GAP + DX + */ (int) (titleRect->size.width - title.width) / 2;
                } else if(alignment == NSLeftTextAlignment) {
                    titleRect->origin.y = iconRect->origin.y + (int)(icon.height / 2) - (int)(title.height / 2);
                    titleRect->origin.x += icon.width + GAP + 2 * DX;
                    title.width = titleRect->size.width - (titleRect->origin.x - iconRect->origin.x);
                }
            }
        }
	iconRect->size = icon;
	titleRect->size = title;
	return self;
}


- (NSRect)imageRectForBounds:(NSRect)iconRect
{
    NSRect titleRect = iconRect;
    [self  _getIconRect:&iconRect  titleRect:&titleRect];
    return iconRect;
}


- (NSRect)titleRectForBounds:(NSRect)titleRect
{
    NSRect iconRect = titleRect;
    [self  _getIconRect:&iconRect  titleRect:&titleRect];
    return titleRect;
}


- (int) hitPart:(NSPoint *) where  inRect:(const NSRect *) cellFrame
{
    NSRect iconRect  = * cellFrame,
           titleRect = * cellFrame;
	
    iconRect = [self imageRectForBounds:iconRect];
    titleRect = [self titleRectForBounds:titleRect];
	
    return NSMouseInRect(*where , titleRect , YES)  ?	IK_TITLEPART:
        NSMouseInRect(*where , iconRect , NO)  ?		IK_ICONPART:
        IK_NOPART;
}


- dragIcon:(NSEvent *)event inRect:(const NSRect *) cellFrame  ofView:view
{
    NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
    NSEvent      *mouseDown = event;
    NSRect	 iconRect = * cellFrame;
    NSPoint	 offset;
    id		 old = delegate;
    id		 theImage = (flags.showMiniImage ? miniImage : image);

    [[view window] setAcceptsMouseMovedEvents:YES];
    while ([event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES] type] ==
           NSLeftMouseDragged) {
        offset.x = [event locationInWindow].x - [mouseDown locationInWindow].x;
        offset.y = [event locationInWindow].y - [mouseDown locationInWindow].y;

        if (abs(offset.x) + abs(offset.y) > HYSTERESIS) {
            [old  addUser:self];
            if (delegate != nil) {
                [delegate  copyToPasteboard:pboard];
            } else {
                [pboard declareTypes:(NSArray *)NULL owner:self];
            }
            iconRect = [self imageRectForBounds:iconRect];
            iconRect.origin.y += iconRect.size.height;

            if (flags.container && !flags.reallyLocked && (!flags.locked || ([event modifierFlags] & NSCommandKeyMask))) {
                [delegate  removeUser:self];
                [self setState:0];
                delegate = nil;
                [self highlight:NO withFrame:iconRect inView:[self controlView]];
            }
            [view dragImage:theImage at:iconRect.origin offset:NSMakeSize(offset.x,offset.y) event:mouseDown pasteboard:pboard source:view slideBack:YES];

            if (delegate == nil  &&  old != nil)
                [self setDelegate:nil];
            [old  removeUser:self];
            break;
        }
    }
    [[view window] setAcceptsMouseMovedEvents:NO];
    return self;
}

- (void)editorFrameChanged:(NSNotification *)arg
{
    [[[arg object] window] invalidateCursorRectsForView:[self controlView]];
}

- editTitle:(NSEvent *)event inRect:(const NSRect *)cellFrame ofView:view
{
    NSRect	titleRect = * cellFrame;
    NSTextView	*editor = [[view window] fieldEditor:YES forObject:self];
    BOOL	canEdit = [self isEditable],
                canSelect = canEdit;
    int		alignment = [self alignment];

    if ([editor delegate] != self) {
        NSSize	 maxSize = { titleRect.size.width,  titleRect.size.height },
                 minSize = {                  0.0,  titleRect.size.height };
 
        [view  endEditing];

        if(alignment == NSCenterTextAlignment) {
            titleRect.size.width += 2000;
            titleRect.origin.x -= 1000;
        }
        titleRect = [self titleRectForBounds:titleRect];
        [editor setDrawsBackground:YES];
        [editor	setAlignment:alignment];
        [editor setTextColor:[NSColor blackColor]];
        [editor setFont:[self font]];

        [editor setEditable:canEdit];
        [editor setSelectable:canSelect];

        [editor	setMaxSize:maxSize];
        [[editor textContainer] setContainerSize:maxSize];
        [editor	setMinSize:minSize];
        [[editor textContainer] setWidthTracksTextView:NO];
        [editor	setHorizontallyResizable:NO];
        [[editor textContainer] setHeightTracksTextView:flags.multipleLines];
        [editor setVerticallyResizable:flags.multipleLines];
        [editor setString:[self stringValue]];

        [editor setDelegate:self];

        if(alignment == NSCenterTextAlignment) {
            titleRect.origin.x = cellFrame->origin.x /* + GAP + DX */;
            titleRect.size.width = cellFrame->size.width;
        } else {
            titleRect.origin.x -= 3;
        }
        [editor setFrame:titleRect];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editorFrameChanged:) name:NSViewFrameDidChangeNotification object:editor];

        [view addSubview:editor];
        [[view window] makeFirstResponder:editor];
    }
    if (event != NULL) {
        [[view window] makeFirstResponder:editor];
        [editor mouseDown:event];
    }
    return self;
}

- (void)textDidEndEditing:(NSNotification *)aNotification
{
    NSString *name;

    if ([self  isEditable]) {
        id sender = [aNotification object];
        if(sender == [self editor]) {
            name = [sender string];
            if([name length] == 0)
                name = @"";
            if (! [name isEqual:[self stringValue]]) {
                [self setStringValue:name];
            }
            [delegate setName:name];
            [sender removeFromSuperview];
            [sender setDelegate:nil];
            [[NSNotificationCenter defaultCenter] removeObserver:self name:NSViewFrameDidChangeNotification object:sender];
        }
    }
}

- editor
{
    id editor = [[[self controlView] window] fieldEditor:NO forObject:self];
    return (editor && [editor delegate] == self) ? editor : nil;
}


@end


static void initGhostImages (NSSize size)
{
    NSSize	imageSize, maskSize;
	
    imageSize = [ghostImage size];
    maskSize = [ghostHighlightMask size];
	
    if (size.width > imageSize.width  ||  size.height > imageSize.height) {
        [ghostImage release];
        ghostImage = [[NSImage alloc] initWithSize:size];
    }

    if (size.width > maskSize.width  ||  size.height > maskSize.height) {
        [ghostHighlightMask release];
        ghostHighlightMask = [[NSImage alloc] initWithSize:size];

        [ghostHighlightMask lockFocus];
        PSsetalpha (1.0 / 3.0);
        PSsetgray (1.0);
        PSrectfill (0.0, 0.0, size.width, size.height);
        [ghostHighlightMask unlockFocus];
    }
}
