
/*
   MiscDragCell.m

   Copyright (C) 1996 Todd Thomas
   Use is governed by the MiscKit license

   This object is included in the MiscKit by permission from the author
   and its use is governed by the MiscKit license, found in the file
   "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
   for a list of all applicable permissions and restrictions.
*/

// RCS identification information
static char *rcsID = "$Id: MiscDragCell.m,v 1.4 1996/10/07 02:49:12 todd Exp $";
static void __AvoidCompilerWarning(void) {if(!rcsID)__AvoidCompilerWarning();}

#import <AppKit/AppKit.h>

#import "MiscAbbreviatedTextCell.h"
#import "IKCellPS.h"
#import "MiscDragCell.h"

// Class ivars
// Keeps track of who the source of a drag is (if it is one of us).
// Use -isSourceDragInProgress to find out whether you are the
// current source in a drag. (instead of accessing this ivar
// or it's class method accessors).
static MiscDragCell* _sourceDragCell = nil;

// Keeps track of who the destination of a drag is (if it is one of us).
// Use -isDestinationDragInProgress to find out whether you are
// the current destination in a drag. (instead of accessing this ivar
// or it's class method accessors).
static MiscDragCell* _destinationDragCell = nil;

// Places to keep dimmed images. Use the class accessors.
static NSImage* _dimmedSourceImage = nil;
static NSImage* _dimmedDestinationImage = nil;

// Current class version for archiving.
static int MiscDragCellCurrentVersion = 0;

// Spacing between text and image (if drag cell displays both).
static int MiscDragCellImageTextSpacing = 3;


@implementation MiscDragCell

/*"
   MiscDragCell is an abstract class that lays the groundwork for
   draggable cells to be used within some kind of drag view (either
   a MiscDragView or MiscDragMatrix subclass). For an example of
   a concrete subclass see MiscFileDragCell, which implements dragging
   using the NSFilenamePboardType.
"*/


//-------------------------------------------------------------------
// 	Class initialization
//-------------------------------------------------------------------

+ (void) initialize
/*"
   Our class initializer. Sets our class version for use with archiving.
"*/
{
    if (self == [MiscDragCell class]) {
        [self setVersion:MiscDragCellCurrentVersion];
    }
}


//-------------------------------------------------------------------
// 	Drag support
//-------------------------------------------------------------------

+ (MiscDragCell*) destinationDragCell
/*"
    Returns the drag cell that's currently acting as the drag
    destination, or nil either if there's no drag going on or the
    destination is some other object. If instances want to know if
    they are the current drag destination they should use our
    #isDestinationDragInProgress instance method.
"*/
{
    return _destinationDragCell;
}


+ (void) setDestinationDragCell:(MiscDragCell*)newDest
/*"
   Sets newDest as the current drag destination. When the
   destination drag ends this method should be called with a
   nil argument.
"*/
{
    _destinationDragCell = newDest;
}


+ (MiscDragCell*) sourceDragCell
/*"
   Returns the drag cell that's currently acting as the drag
   source, or nil either if there's no drag going on or the
   source is some other object (that is not a subclass of MiscDragCell).
   If instances want to know if they are the current drag source
   they should use our #isSourceDragInProgress instance method.
"*/
{
    return _sourceDragCell;
}


+ (void) setSourceDragCell:(MiscDragCell*)newSource
/*"
   Sets newSource as the current drag source. When the source
   drag ends this method should be called with a nil argument.
"*/
{
    _sourceDragCell = newSource;
}


+ (NSImage*) dimmedSourceImage
/*"
   Used internally. Returns our dimmed source image. Since
   only a single drag can be taking place at any time I decided
   to use a class variable to hold the image.
"*/
{
    return _dimmedSourceImage;
}


+ (void) setDimmedSourceImage:(NSImage*)newImage
/*"
   Sets our dimmed source image to newImage.
"*/
{
    [_dimmedSourceImage autorelease];
    _dimmedSourceImage = [newImage copy];
}


+ (NSImage*) dimmedDestinationImage
/*"
   Used internally. Returns our dimmed destination image.
   Since only a single drag can be taking place at any time
   I decided to use a class variable to hold the image.
"*/
{
    return _dimmedDestinationImage;
}


+ (void) setDimmedDestinationImage:(NSImage*)newImage
/*"
   Used internally. Sets our current dimmed image for the
   destination drag cell.
"*/
{
    [_dimmedDestinationImage autorelease];
    _dimmedDestinationImage = [newImage copy];
}


//-------------------------------------------------------------------
// 	Initialization / deallocation
//-------------------------------------------------------------------

- init
/*"
   Our designated initializer. Since #initTextCell: and #initImageCell:
   don't seem to make much sense for a drag cell you have to use this
   method. Calling either of the other two more common NSCell initializer
   methods will result in a run-time error.
"*/
{
    BOOL error = ([super init] == nil);

    if (!error) {
        // Background color is only used if we draw a border.
        [self setBackgroundColor:[NSColor lightGrayColor]];
        _imageCell = [[NSCell alloc] init];
        _textCell = [[MiscAbbreviatedTextCell alloc] initTextCell:@""];
        [self setTitle:@""];
        [self setAllowsSourceDragging:YES];
        [self setAllowsDestinationDragging:YES];
        [self setShadowsIncoming:YES];
    }

    return error ? nil : self;
}


- initTextCell:(NSString*)text
/*"
   Calls #doesNotRecognizeSelector:. Use init instead.
"*/
{
    NSLog(@"[MiscDragCell initTextCell:] Use -init as the designated initializer.");
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}


- (id) initImageCell:(NSImage*)image
/*"
   Calls #doesNotRecognizeSelector:. Use init instead.
"*/
{
    NSLog(@"[MiscDragCell initImageCell:] Use -init as the designated initializer.");
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}


- (void) dealloc
/*"
   Releases our resources.
"*/
{
    [_acceptingImage release];
    [_imageCell release];
    [_textCell release];
    [_backgroundColor release];
    
    [super dealloc]; 
}


//-------------------------------------------------------------------
// 	NSCell overrides
//-------------------------------------------------------------------

- (BOOL) acceptsFirstResponder
/*"
   Drag Views and Cells cannot be manipulated by anything other than
   the mouse so they don't accept first responder.
"*/
{
    return NO;
}


- (BOOL) isOpaque
/*"
   If we have a border then we cover our entire area (and return YES).
   If we don't, we won't (returns NO).
"*/
{
    return ([self borderType] != NSNoBorder);
}


//-------------------------------------------------------------------
// 	Internal Cell accessors
//-------------------------------------------------------------------

- (NSCell*) textCell
/*"
   Returns the cell we use to draw our title. You shouldn't need to
   use this method except in other methods like #prepareTextCellForDisplay.
"*/
{
    return _textCell;
}


- (void) setTextCell:(NSCell*)newTextCell
/*"
   Sets the cell we use to draw our title. By default it is an
   instance of MiscAbbreviatedTextCell, which takes care of
   putting ".." at the end of titles that are too long to display.
"*/
{
	[_textCell autorelease];
	_textCell = [newTextCell retain];
}


- (NSCell*) imageCell
/*"
   Returns the cell we use to draw our image. You shouldn't need to
   use this method unless you create a subclass and want to use a
   custom cell.
"*/
{
    return _imageCell;
}


- (void) setImageCell:(NSCell*)newImageCell
/*"
   Sets the image cell we use to draw our image. The default is
   just an NSCell initialized with #initImageCell:.
"*/
{
	[_imageCell autorelease];
	_imageCell = [newImageCell retain];
}


//-------------------------------------------------------------------
// 	Our images
//-------------------------------------------------------------------

- (NSImage*) acceptingImage
/*"
   Returns the image that's dislayed when we are accepting an
   image, or nil if there is no accepting image. This method
   overrides any value returned by our #shadowsIncoming method. 
"*/
{
    return _acceptingImage;
}


- (void) setAcceptingImage:(NSImage *)newImage
/*"
   Sets the image that's displayed when we are the destination of a
   drag. This would be useful for instance if we were trying to
   write a folder subclass that showed the open folder when it
   accepts the drag.
"*/
{		
    [_acceptingImage autorelease];
    _acceptingImage = [newImage copy];
}


- (NSImage*) image
/*"
   Returns the image we display or nil if we don't have an image.
"*/
{
    return _image;
}


- (void) setImage:(NSImage*)newImage
/*"
   Sets the image we display. If newImage is nil we set our title
   to be the empty string.
"*/
{
    [_image autorelease];
    _image = [newImage copy];

    // There can be no title if there isn't an image.
    if (newImage == nil) {
        [self setTitle:@""];
    }
}


//-------------------------------------------------------------------
// 	Title manipulation
//-------------------------------------------------------------------

- (NSString*) title
/*"
   Returns our current title. This may or may not be displayed,
   depending upon what #displaysTitle returns.
"*/
{
    return _title;
}


- (void) setTitle:(NSString*)newTitle
/*"
   Sets our title. This may or may not be displayed under our image
   depending on what our #displaysTitle method returns.
   returns YES.
"*/
{
    newTitle = (newTitle != nil) ? newTitle : @"";
    [_title autorelease];
    _title = [newTitle copy];
}


- (BOOL) displaysTitle
/*"
   Returns YES if we display our title under our image.
"*/
{
    return _displaysTitle;
}


- (void) setDisplaysTitle:(BOOL)displays
/*"
   Sets whether we display a title.
"*/
{
    _displaysTitle = displays;
}


- (BOOL) isTitleEditable
/*"
   Titles aren't currently editable so the return value doesn't really matter.
"*/
{
    return _titleEditable;
}


- (void) setTitleEditable:(BOOL)isEditable
/*"
   Not implemented yet.
"*/
{
    _titleEditable = isEditable;
}


- (BOOL) isTitleMultiline
/*"
    Not implemented yet.
"*/
{
    return _titleMultiline;
}


- (void) setTitleMultiline:(BOOL)isMultiline
/*"
    Not implemented yet.
"*/
{
    _titleMultiline = isMultiline;
}


//-------------------------------------------------------------------
// 	Dragging options
//-------------------------------------------------------------------

- (BOOL) allowsSourceDragging
/*"
   Returns YES if we can be the source of a dragging session, or NO
   if we cannot.
"*/
{
    return _allowSourceDragging;
}


- (void) setAllowsSourceDragging:(BOOL)aBool
/*"
    Sets whether we can be the source of a dragging session.
"*/

{
    _allowSourceDragging = aBool;	
}

 
- (BOOL) allowsDestinationDragging
/*"
    Returns YES if we are allowed to accept a drag.
    The default is YES.
"*/
{
    return _allowDestinationDragging;
}

	
- (void) setAllowsDestinationDragging:(BOOL)aBool
/*"
    Sets whether we are allowed to accept a drag.
    The default is YES.
"*/
{
    _allowDestinationDragging = aBool;
}


- (BOOL) acceptsForeignDrag
/*"
   Returns YES if we accept drags that don't originate from
   within our own application. The default is YES.
   Subclasses can override this to change this behavior.
"*/
{

    return YES;
}


- (BOOL) acceptsLocalDrag
/*"
   Returns YES if we accept drags that originate from
   within our own application. The default is YES.
   Subclasses can override this to change this behavior.
"*/
{
    return YES;
}


- (BOOL) acceptsSelfDrag
/*"
   Returns YES if we accept drags that originated from
   our own drag cell. The default is YES. You see this
   behavior in the Workspace shelf. If you drag a file
   from one of the cells you are still able to put it back
   in the same cell. If this method returns YES then
   #acceptsLocalDrag should also return YES.
   Subclasses can override this to change this behavior.
"*/
{
    if ([self allowsDestinationDragging] == NO) {
        return NO;
    }
    return YES;
}


- (BOOL) retainsData
/*"
   Returns YES if when we drag that we leave a copy of
   ourselves behind. If you were to emulate the Workspace
   shelf then this would return NO. The default is NO.
   Subclasses can override this to change this behavior.
"*/
{
    return NO;
}


- (BOOL) shadowsIncoming
/*"
   Returns YES if we show a dimmed image when there is
   an incoming drag. This assumes that we are currently
   accepting drags. The default is YES.
"*/
{
    return _shadowIncoming;
}


- (void) setShadowsIncoming:(BOOL)nowShadow
/*"
    Sets whether we show a dimmed image when there is
    an incoming drag. The default is YES.
"*/
{	
    _shadowIncoming = nowShadow;
}


- (NSColor *) shadowColor
/*"
    Returns the color used to created a dimmed appearance
    for a destination image.  The default is to return a partially
    transparent gray.  If you want a different appearance for shadowing,
    you can override this method.
"*/
{
    NSColor* shadowColor = [NSColor lightGrayColor];
    shadowColor = [shadowColor colorWithAlphaComponent:0.5];
    return shadowColor;
}


- (BOOL) dragImageSlidesBack
/*"
   Returns YES if the dragging image should slide back to it's destination
   when it isn't deposited in another view. The default is to slide back
   only if we retain our data (#retainsData returns YES).
"*/
{
    return [self retainsData];
}


//-------------------------------------------------------------------
// 	Destination dragging
//-------------------------------------------------------------------

- (NSArray*) acceptingPasteboardTypes
/*"
   This method is for subclasses to override to return the pasteboard
   types that it will accept (as the destination of a drag). In
   contrast, for a source drag you don't have to define what you will
   drag. You can put data of any type on the pasteboard in our
   #prepareForSourceDrag method. Our implementation returns nil.
"*/
{
	return nil;
}


- (void) cleanupAfterDestinationDrag
/*"
   Internally used. This is called after a destination drag
   whether it was a success or failure.
"*/
{
    [[self class] setDimmedDestinationImage:nil];
    // If we are both the source and destination then we'll
    // take care of setting the dest cell to nil
    if (![self isSourceDragInProgress]) {
        [[self class] setDestinationDragCell:nil];
    }
    if ([self image] == nil) {
        [self setTitle:@""];
    }
}


//-------------------------------------------------------------------
// 	Dragging pasteboard
//-------------------------------------------------------------------

- (NSPasteboard*) draggingPasteboard
/*"
   Override this if you would like to use a different pasteboard for
   dragging. The default is the NSDragPboard.
"*/
{
    return [NSPasteboard pasteboardWithName:NSDragPboard];
}


//-------------------------------------------------------------------
// 	Source dragging
//-------------------------------------------------------------------

- (BOOL) prepareForSourceDrag
/*"
   This method is meant to be overridden in subclasses to allow you
   to put the data you want to send on the dragging pasteboard. Our
   implementation returns NO.
"*/
{
    return NO;
}


#define STICKINESS 5.0

- (BOOL) trackMouse:(NSEvent*)theEvent inRect:(NSRect)rect 
	ofView:(NSView*)controlView untilMouseUp:(BOOL)untilMouseUp;
/*"
   We take care of starting a source drag, as long as the following
   conditions are met: (1) We allow source dragging, (2) our
   image is not nil, (3) prepareForSourceDrag returns YES, and
   the mouse is moved at least 5 pixels from where the original
   mouse down occured. Returns YES no matter what happens, though
   I'm not sure if that's correct.
"*/
{
    NSEvent *origEvent=nil, *loopEvent;
    NSPoint  zero = {0.0, 0.0};
    NSPoint  origPoint, newLoc;
    float  distance = 0.0;
    BOOL error = NO;
    
    if (!error && ([self image] == nil || ![self allowsSourceDragging])) {
        error = YES;
    }

    // if the overridden prepareForSourceDrag returns YES continue
    if (!error) {
        error = ![self prepareForSourceDrag];
    }

    // The mouse moved more than STICKINESS pixels code orignally
    // came from Mike Ferris's MODocumentWell class in the MOKit.
    if (!error) {
        origPoint = [theEvent locationInWindow];
        origPoint = [controlView convertPoint:origPoint fromView:nil]; 	
        origEvent = theEvent;		

        [[controlView window] setAcceptsMouseMovedEvents:YES];

        loopEvent = [[controlView window] nextEventMatchingMask:
            (NSLeftMouseUpMask | NSLeftMouseDraggedMask)];

        while ([loopEvent type] != NSLeftMouseUp && distance < STICKINESS) {
            newLoc = [loopEvent locationInWindow];
            newLoc = [controlView convertPoint:newLoc fromView:nil];

            // if we've gone at least STICKINESS units from the original
            // mousedown, start dragging our file.
            distance = sqrt(((newLoc.x - origPoint.x) * (newLoc.x - origPoint.x)) +
                            ((newLoc.y - origPoint.y) * (newLoc.y - origPoint.y)));

            loopEvent = [[controlView window] nextEventMatchingMask:
                (NSLeftMouseUpMask | NSLeftMouseDraggedMask)];		
        }

        [[controlView window] setAcceptsMouseMovedEvents:NO];

        // If the user didn't drag far enough then don't continue.
        error = (distance < STICKINESS);
    }

    if (!error) {
        NSImage* dragImage = [self imageToDrag];

        [self calculateDragPoint:&origPoint andOffset:&zero];

        if (![self retainsData]) {
            [self createDimmedSourceImage];
        }

        [[self class] setSourceDragCell:self];
        [controlView dragImage:dragImage
                            at:origPoint
                        offset:NSMakeSize((&zero)->x,(&zero)->y)
                         event:origEvent
                    pasteboard:[self draggingPasteboard]
                        source:self
                     slideBack:[self dragImageSlidesBack]];		

        [[self class] setSourceDragCell:nil];
        [[self class] setDimmedSourceImage:nil];

        // Update ourselves.
        [(NSControl*)controlView drawCell:self];
    }

    // Does it even matter what I return?
    return YES;
}


- (void) calculateDragPoint:(NSPoint*)dragPoint andOffset:(NSPoint*)offset
/*"
   This method should be overridden if you want to have some control
   on where the dragged image is first placed, and how far it is offset
   from the original mousedown (dragPoint).
   Making the dragPoint be the middle of the image is the default,
"*/
{
    NSSize imageSize = [[self imageToDrag] size];

    dragPoint->x -= imageSize.width/2.0;
    dragPoint->y -= imageSize.height/2.0;
    if ([[self controlView] isFlipped]) {
        dragPoint->y += imageSize.height;
    }
}


//-------------------------------------------------------------------
// 	Drag in progress?
//-------------------------------------------------------------------

- (BOOL) isSourceDragInProgress
/*"
   Returns YES if we are currently the source of a drag. This
   is most often used so we can draw shadowed images, etc. in
   our drawing methods.
"*/
{
    return ([[self class] sourceDragCell] == self);
}


- (BOOL) isDestinationDragInProgress
/*"
   Returns YES if we are currently the destination of a drag.
   This is most often used so we can draw shadowed images, etc.
   in our drawing methods.
"*/
{
    return ([[self class] destinationDragCell] == self);
}


//-------------------------------------------------------------------
// 	Sizing ourselves
//-------------------------------------------------------------------

- (void) calcDrawInfo:(NSRect)rect
/*"
   Doesn't do anything yet.
"*/
{
    NSLog (@"[NSDragCell calcDrawInfo:] was called.");
}

- (NSSize) cellSize
/*"
   Returns our current cell size.
"*/
{
    // TEMPORARY
    NSSize minCellSize = NSMakeSize (64.0, 64.0);
    if ([self displaysTitle]) {
        minCellSize.height += 10.0;
        minCellSize.width += 10.0;
    }
    NSLog (@"[NSDragCell cellSize] was called.");
    return minCellSize;
}

- (NSSize) cellSizeForBounds:(NSRect)bounds
/*"
   This method hasn't been implemented correctly yet.
   It just returns the size from our #cellSize method.
"*/
{
    NSLog (@"[NSDragCell cellSizeForBounds:] was called.");
    return [self cellSize];;
}

- (NSRect) drawingRectForBounds:(NSRect)bounds
/*"
   This method hasn't been implemented correctly yet.
"*/
{
    NSLog (@"[NSDragCell drawingRectForBounds:] was called.");
    return [super drawingRectForBounds:bounds];
}

- (NSRect) imageRectForBounds:(NSRect)bounds
/*"
   This method hasn't been implemented correctly yet.
"*/
{
    NSLog (@"[NSDragCell imageRectForBounds:] was called.");
    return [[self imageCell] imageRectForBounds:bounds];
}

- (NSRect) titleRectForBounds:(NSRect)bounds
/*"
   This method hasn't been implemented correctly yet.
"*/
{
    NSLog (@"[NSDragCell titleRectForBounds:] was called.");
    return [[self textCell] titleRectForBounds:bounds];
}


//-------------------------------------------------------------------
// 	Display options
//-------------------------------------------------------------------

- (NSColor*) backgroundColor
/*"
   Returns the background color we use if happen to draw our
   background (which we only do if we have a border).
   Otherwise we are non-opaque and our view behind us will end
   up drawing our background. By default our background color
   is light gray.
"*/
{
    return _backgroundColor;
}


- (void) setBackgroundColor:(NSColor*)newColor
/*"
   Sets our background color. By default it is light gray.
   See our #backgroundColor method to see when we use
   our background color.
"*/
{
    if (newColor != nil) {
        [_backgroundColor autorelease];
        _backgroundColor = [newColor retain];
    }
}


- (BOOL) isBezeled
/*"
   Overridden from NSCell since we are using the NSBox-like
   methods #{setBorderType:}/#{borderType} to determine our
   border. We return YES if our  border type is NSBezelBorder.
"*/
{
    return ([self borderType] == NSBezelBorder);
}


- (void) setBezeled:(BOOL)nowBezeled
/*"
   If nowBezeled is YES we will draw an NSBezelBorder. If
   nowBezeled is NO we set our border type to NSNoBorder.
"*/
{
    [self setBorderType:(nowBezeled ? NSBezelBorder : NSNoBorder)];
}


- (BOOL) isBordered
/*"
    Overridden from NSCell since we are using the NSBox-like
    methods #{setBorderType:}/#{borderType} to determine our
    border. We return YES if our  border type is NSLineBorder.
"*/
{
    return ([self borderType] == NSLineBorder);
}


- (void) setBordered:(BOOL)nowBordered
/*"
   If nowBordered is YES we will draw an NSLineBorder. If
   nowBordered is NO we set our border type to NSNoBorder.
"*/
{
    [self setBorderType:(nowBordered ? NSLineBorder : NSNoBorder)];
}


- (NSBorderType) borderType
/*"
   Returns our border type, which will be one of NSNoBorder
   (the default), NSBezelBorder, NSLineBorder, or NSGrooveBorder.
"*/
{
    return _borderType;
}


- (void) setBorderType:(NSBorderType)aType
/*"
   Sets our border type. aType should be one of NSNoBorder,
   NSBezelBorder, NSLineBorder or NSGrooveBorder.
"*/
{
    _borderType = aType;
}


- (NSImage*) imageToDisplay
/*"
   Returns the image we should be displaying in our cell.
   This is checked every time we are asked to draw ourselves.
   If we are not the middle of a drag then we just display
   our image (as returned by [self image]). Otherwise if
   we are the destination drag cell we can either return
   our accepting image (if we have one) or a dimmed
   destination image (if #shadowsIncoming returns YES).
   If we are the current source drag cell then we'll either
   return our own image, or a dimmed source image
   (if we don't retain our data).
"*/
{
    NSImage* imageToDisplay;
    
    // If we are not in the middle of a source or destination
    // drag then we display our image by default.
    imageToDisplay = [self image];

    // Choose the image to display if we are the destination
    // drag view.
    if ([self isDestinationDragInProgress]) {

        // The accepting image (if there is one) overrides
        // shadowing the incoming image.
        if ([self acceptingImage] != nil) {
            imageToDisplay = [self acceptingImage];
        }
        else if ([self shadowsIncoming]) {
            imageToDisplay = [[self class] dimmedDestinationImage];
        }
    }

    // Choose the image to display if we are the source
    // drag view. This must be after the destination drag
    // image choice because we may be both the source and
    // destination of a drag at the same time (and the
    // source image overrides).
    if ([self isSourceDragInProgress]) {
        if (![self retainsData]) {
            imageToDisplay = [[self class] dimmedSourceImage];
        }
        else {
            imageToDisplay = [self image];
        }
    }

    return imageToDisplay;
}


- (NSString*) titleToDisplay
/*"
	Returns the title that'll be displayed along with our image as long
	as #displaysTitle returns YES. By default we just return [self title]
	but subclasses can easily modify this method.
"*/
{
    NSString* titleToDisplay = [self title];
    if (titleToDisplay == nil) {
        titleToDisplay = @"";
    }
    
    return titleToDisplay;
}


- (NSImage*) imageToDrag
/*"
	By default we just return [self image]. Subclasses can alter this
	to return a different image. For instance, a subclass that drags
	TIFF images could override this method to return the standard TIFF
	icon.
"*/
{
	return [self image];
}


//-------------------------------------------------------------------
// 	Dimmed image creation
//-------------------------------------------------------------------

- (void) createDimmedSourceImage
/*"
   Creates our dimmed source image by copying our image and
   compositing our shadow color over it. This copy can be
   retrieved by calling [[self class] dimmedSourceImage].
"*/
{
    NSImage* sourceImage = [[self image] copy];
    NSSize sdiSize = [sourceImage size];

    [sourceImage lockFocus];
    [[self shadowColor] set];
    PScompositerect(0.0, 0.0, sdiSize.width, sdiSize.height, NSCompositeSourceAtop);
    [sourceImage unlockFocus];

    // This might be better served as an instance method.
    [[self class] setDimmedSourceImage:sourceImage];
    [sourceImage release];
}


- (void) createDimmedDestinationImageUsingImage:(NSImage*)origImage
/*"
   Creates our dimmed destination image by copying origImage and
   compositing our shadow color over it. This copy can be
   retrieved by calling [[self class] dimmedDestinationImage].
"*/
{
    NSImage* draggingImage = [origImage copy];
    NSSize ddiSize;

    ddiSize = [draggingImage size];

    [draggingImage lockFocus];
    // Dim the image.
    [[self shadowColor] set];
    PScompositerect(0.0, 0.0, ddiSize.width, ddiSize.height,
                    NSCompositeSourceAtop);
    [draggingImage unlockFocus];

    [[self class] setDimmedDestinationImage:draggingImage];
    [draggingImage release];
}


//-------------------------------------------------------------------
// 	Display hooks
//-------------------------------------------------------------------

- (void) prepareTextCellForDisplay
/*"
   This method is called just before the text is drawn. We
   currently just set the cell's string value to the title
   we are displaying (whether we really display it or not
   depends on the value returned by #displaysTitle). If
   you extend this method make sure and do a [super
   prepareTextCellForDisplay].
"*/
{
    [[self textCell] setStringValue:[self titleToDisplay]];
}


//-------------------------------------------------------------------
// 	Drawing
//-------------------------------------------------------------------

- (void) drawWithFrame:(NSRect)frame inView:(NSView*)controlView
/*"
   Draws the border if there is one, then offsets the NSRect and passes it
   to #drawInteriorWithFrame:inView:.
"*/
{
    NSRect interiorFrame = frame;
    
    switch ([self borderType]) {
        case NSNoBorder:
            break;

		// All these cases haven't been tested yet so this may not
		// be correct. That's why they're all the same right now.
		// Of course if they turn out to be all the same offset
		// then this'll become an if-then statement.
        case NSBezelBorder:
            interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0);
            NSDrawGrayBezel (frame, frame);
            break;

        case NSGrooveBorder:
            interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0);
            NSDrawGroove (frame, frame);
            break;

        case NSLineBorder:
            interiorFrame = NSInsetRect (interiorFrame, 2.0, 2.0);
            NSFrameRect (frame);
            break;
    }

    [self drawInteriorWithFrame:interiorFrame inView:controlView];
}

            
- (void) drawInteriorWithFrame:(NSRect)frame inView:(NSView*)controlView
/*"
   Draws our background if we have a border (using our background color).
   We then draw our image and title (if #displaysTitle returns YES) if
   we have one.
"*/
{
    NSImage* imageToDisplay = [self imageToDisplay];
    NSRect imageFrame;
    NSSize imageSize;
	NSCell* textCell = [self textCell];
	NSRect textFrame;
	
    // If we have a border we have to draw our entire background.
    if ([self borderType] != NSNoBorder) {
        [[self backgroundColor] set];
        NSRectFill (frame);
    }
    
    // Figure out where image and text (if displayed) go. If there's
	// text to be drawn it will only affect the y placement of the
	// image.
	imageSize = [imageToDisplay size];
	
    if ([self displaysTitle] && imageToDisplay != nil) {
//        NSCell* textCell = [self textCell];
//        NSRect textFrame;
        float textAndImageHeight;

        [self prepareTextCellForDisplay];
        textFrame.size = [textCell cellSize];

        // Don't allow us to draw beyond our bounds.
        if (NSWidth (textFrame) > NSWidth (frame)) {
            textFrame.size.width = NSWidth (frame);
        }
        
		textAndImageHeight = (NSHeight(textFrame) + imageSize.height +
                        MiscDragCellImageTextSpacing);
        textFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - textAndImageHeight) / 2.0);
        if ([controlView isFlipped]) {
            textFrame.origin.y += textAndImageHeight - NSHeight(textFrame);
        }
//		textFrame.origin.x = NSMinX(frame) + ((NSWidth(frame) - NSWidth(textFrame)) / 2.0);
        textFrame.origin.x = frame.origin.x;

        if ([controlView isFlipped]) {
            imageFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - textAndImageHeight) / 2.0);
        }
        else {
            imageFrame.origin.y = textFrame.origin.y + NSHeight(textFrame) +
                             MiscDragCellImageTextSpacing;
        }
    }
    else {
        imageFrame.origin.y = NSMinY(frame) + ((NSHeight(frame) - imageSize.height) / 2.0);	
	}

    // Calculate where image shuld go. (Needed to do highlighting below).
    imageFrame.size = imageSize;
    imageFrame.origin.x = NSMinX(frame) + ((NSWidth(frame) - imageSize.width) / 2.0);

	// Draw highlighting.
    if ([self isHighlighted] && [self image] != nil) {
        [[NSColor whiteColor] set];
        PSiconBackdrop (imageFrame.origin.x - 4.0, imageFrame.origin.y - 4.0, NSWidth(imageFrame) + 8.0, NSHeight(imageFrame) + 8.0);
    }
	
	// Draw title.
	[textCell highlight:[self isHighlighted] withFrame:textFrame inView:controlView];
    [textCell drawWithFrame:textFrame inView:controlView];

    // Draw image.
    [[self imageCell] setImage:imageToDisplay];
    [[self imageCell] drawWithFrame:imageFrame inView:controlView];

}


//-------------------------------------------------------------------
// 	Copying
//-------------------------------------------------------------------

- (id) copyWithZone:(NSZone*)zone
/*"
   Returns a copy of the receiver.
"*/
{
    id copy = [[[self class] allocWithZone:zone] init];

    [copy setAcceptingImage:[self acceptingImage]];
    [copy setImage:[self image]];
    [copy setTitle:[self title]];
    [copy setBorderType:[self borderType]];
    [copy setBackgroundColor:[self backgroundColor]];
    [copy setAllowsSourceDragging:[self allowsSourceDragging]];
    [copy setAllowsDestinationDragging:[self allowsDestinationDragging]];
    [copy setShadowsIncoming:[self shadowsIncoming]];
    [copy setDisplaysTitle:[self displaysTitle]];
    [copy setTitleEditable:[self isTitleEditable]];
    [copy setTitleMultiline:[self isTitleMultiline]];
    return copy;
}


//-------------------------------------------------------------------
// 	Archiving methods
//-------------------------------------------------------------------

- (id) initWithCoder:(NSCoder*)aDecoder
/*"
   Unarchives an instance of MiscDragCell.
"*/
{
    int version;

    [super initWithCoder:aDecoder];

    version = [aDecoder versionForClassName:@"MiscDragCell"];
    
    switch (version) {
      case 0:	// Current version
          [self setAcceptingImage:[aDecoder decodeObject]];
          [self setImageCell:[aDecoder decodeObject]];
          [self setImage:[aDecoder decodeObject]];
          [self setTextCell:[aDecoder decodeObject]];
          [self setTitle:[aDecoder decodeObject]];
          [self setBackgroundColor:[aDecoder decodeObject]];
          [aDecoder decodeValueOfObjCType:@encode(NSBorderType) at:&_borderType];
          [aDecoder decodeValuesOfObjCTypes:"ccc", &_allowSourceDragging,
			&_allowDestinationDragging, &_shadowIncoming];
          [aDecoder decodeValuesOfObjCTypes:"ccc", &_displaysTitle,
             &_titleEditable, &_titleMultiline];

        break;

      default:
        NSLog(@"[%@ %@] - unknown version found in initWithCoder:.",
              NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        break;
    }

    return self;
}


- (void) encodeWithCoder:(NSCoder*)aCoder
/*"
   Archives an instance of MiscDragCell.
"*/
{
    [super encodeWithCoder:aCoder];
    
    [aCoder encodeObject:[self acceptingImage]];
    [aCoder encodeObject:[self imageCell]];
    [aCoder encodeObject:[self image]];
    [aCoder encodeObject:[self textCell]];
    [aCoder encodeObject:[self title]];
    [aCoder encodeObject:[self backgroundColor]];
    [aCoder encodeValueOfObjCType:@encode(NSBorderType) at:&_borderType];
	[aCoder encodeValuesOfObjCTypes:"ccc", &_allowSourceDragging,
		&_allowDestinationDragging, &_shadowIncoming];
    [aCoder encodeValuesOfObjCTypes:"ccc", &_displaysTitle,
        &_titleEditable, &_titleMultiline];
}

@end


@implementation MiscDragCell (NSDraggingSource)

- (unsigned int) draggingSourceOperationMaskForLocal:(BOOL)flag
/*"
   By default we return NSDragOperationAll. Subclasses can
   override to customize behavior.
"*/
{
    return NSDragOperationAll;
}


- (void) draggedImage:(NSImage*)image endedAt:(NSPoint)screenPoint deposited:(BOOL)deposited
/*"
   Takes care of details after the image has been deposited.
"*/
{
    // If we don't retain our data then get rid of it.
    if (![self retainsData]) {
        // If we deposited somewhere else besides back on ourselves
        // then get rid of the image.
        if (deposited) {
            if ([self isDestinationDragInProgress]) {
                // If we are both the source and dest then we're
                // responsible for setting this (if the dest sets it
                // then our if statement wouldn't have worked because
                // the dest drag cell would have already been reset).
                [[self class] setDestinationDragCell:nil];
            }
            else {
                [self setImage:nil];
            }
        }
    }
}

@end


@implementation MiscDragCell (NSDraggingDestination)

- (unsigned int) draggingEntered:(id <NSDraggingInfo>)sender
/*"
   Checks to see whether we should accept the drag by checking
   whether we allow destination dragging, whether we accept
   self drags, foreign drags or local drags and returns NO
   if any of the above conditions don't agree with our
   instance's current settings.
"*/
{
    if ([self allowsDestinationDragging] == NO) {
        return NSDragOperationNone;
    }	
    if ([self acceptsSelfDrag] == NO && [sender draggingSource] == self) {
        return NSDragOperationNone;
    }
    if ([self acceptsForeignDrag] == NO && [sender draggingSource] == nil) {
        return NSDragOperationNone;
    }
    if ([self acceptsLocalDrag] == NO && [sender draggingSource] != nil) {
		return NSDragOperationNone;
    }

    [[self class] setDestinationDragCell:self];

    if ([self shadowsIncoming]) {
        [self createDimmedDestinationImageUsingImage:[sender draggedImage]];

        // Display our new dimmed image.
        [(NSControl*)[self controlView] drawCell:self];
    }

    return NSDragOperationCopy;
}


- (unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender
/*"
   By default we just return NSDragOperationCopy (and I'm not
   sure why anymore).
"*/
{	
    return NSDragOperationCopy;
}


- (void) draggingExited:(id <NSDraggingInfo>)sender
/*"
   Calls our #cleanupAfterDestinationDrag and asks
   our control view to draw ourselves again. If you
   extend this method make sure to call [super
   draggingExited:sender];.
"*/
{
    [self cleanupAfterDestinationDrag];
    [(NSControl*)[self controlView] drawCell:self];
}


- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender
/*"
   Returns YES.
"*/
{
    return YES;
}


- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
/*"
   Returns YES.
"*/
{	
    return YES;
}


- (void) concludeDragOperation:(id <NSDraggingInfo>)sender
/*"
    Calls our #cleanupAfterDestinationDrag and asks
    our control view to draw ourselves again. If you
    extend this method make sure to call [super
    concludeDragOperation:sender];.
"*/
{
    [self cleanupAfterDestinationDrag];
    [(NSControl*)[self controlView] drawCell:self];
}

@end
