
/*
    MiscFileDragCell.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: MiscFileDragCell.m,v 1.4 1996/10/07 02:49:20 todd Exp $";
static void __AvoidCompilerWarning(void) {if(!rcsID)__AvoidCompilerWarning();}

#import <AppKit/AppKit.h>
#import <MiscFoundation/NSString+MiscExtensions.h>

#import "MiscFileDragCell.h"

// Class ivars
static NSString* _tempTitleToDisplay = nil;

// Private Constants
static NSString* MiscFileDragCellClassName = @"MiscFileDragCell";
#define MISC_FILE_DRAG_CELL_VERSION 0


@implementation MiscFileDragCell

/*"
   MiscFileDragCell is for dragging files using the NSFilenamePboardType.
   This is the same type that Workspace and most other applications
   that allow you to drag filenames use.
   It is used by both MiscFileDragView and MiscFileDragMatrix.

   Use our #setFilename: and/or #setFilenames: to set the images and files
   we represent and display. Don't use #setImage: because you'll end up
   displaying an image and have no filename to go along with it.
"*/


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

+ (void) initialize
/*"
   Our class initializer. We set our class version (for archiving).
"*/
{
    if (self == [MiscFileDragCell class]) {
        [self setVersion:MISC_FILE_DRAG_CELL_VERSION];
    }
}


//-------------------------------------------------------------------
// 	Temporary title holder
//-------------------------------------------------------------------

+ (NSString*) tempTitleToDisplay
/*"
   Returns the temporary title we should be displaying as long
   as we are the current destination drag cell. Since there can
   only be a single drag going on at any one time I chose to
   use a class ivar to hold temporary titles.
"*/
{
    return _tempTitleToDisplay;
}


+ (void) setTempTitleToDisplay:(NSString*)newTempTitle
/*"
   Sets the temporary title that the current destination drag
   cell will display.
"*/
{
    [_tempTitleToDisplay release];
    _tempTitleToDisplay = [newTempTitle retain];
}


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

- (id) init
/*"
   Our designated initializer. Sets ourselves to display a title
   and allow a double click to launch the file(s).
"*/
{
    BOOL error = ([super init] == nil);

    if (!error) {
        [self setAllowsDoubleClickLaunch:YES];
        [self setDisplaysTitle:YES];
    }

    return error ? nil : self;
}


- (void) dealloc
/*"
   Releases our resources.
"*/
{
    [_filenames release];
    [super dealloc];
}


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

- (id) copyWithZone:(NSZone*)zone
/*"
   Returns a copy of our instance.
"*/
{
    // Our superclass makes a deep copy.
    id copy = [super copyWithZone:zone];

    [copy setFilenames:[self filenames]];
    [copy setAllowsDoubleClickLaunch:[self allowsDoubleClickLaunch]];
    return copy;
}


//-------------------------------------------------------------------
// 	Acceptable types
//-------------------------------------------------------------------

- (NSArray*) acceptingPasteboardTypes
/*"
   Returns an array with a single pastboard type:
   NSFilenamesPboardType.
"*/
{	
    return [NSArray arrayWithObject:NSFilenamesPboardType];
}


//-------------------------------------------------------------------
// 	Our contents
//-------------------------------------------------------------------

- (NSString *) filename
/*"
   Returns the first filename in our filenames array
   or an empty string if we don't currently have a filename.
"*/
{
    NSString* firstFilename = @"";
    
    if ([[self filenames] count] > 0) {
        firstFilename = [[self filenames] objectAtIndex:0];
    }
    return firstFilename;
}


- (void) setFilename:(NSString*)aFilename
/*"
   Sets our current filename to aFilename. If aFilename is nil
   or the empty string then our image and title will be cleared.
"*/
{
    [self setFilenames:[NSArray arrayWithObject:aFilename]];
    if (![aFilename hasNonEmptyString]) {
        [self setTitle:@""];
    }
}


- (NSArray*) filenames
/*"
   Returns the filenames we represent. If you know for sure that it
   is only a single filename then you could probably use our
   #filename method. If we don't currently represent any filenames
   then nil is returned (or is it the empty array).
"*/
{
    return _filenames;
}


- (void) setFilenames:(NSArray*)newFilenames
/*"
   Sets our filenames to newFilenames and asks the NSWorkspace
   for our images. If newFilenames is nil or an empty array then our
   image and title will be cleared.
"*/
{
    // We'll retain newFilenames at the bottom of the method if workspace
    // could find an image. If not then there's nothing to display.
    [_filenames autorelease];
    _filenames = nil;
    // No filename, no title.
    [self setTitle:@""];
    
    if ([newFilenames count] == 1) {
        [self setImage:[[NSWorkspace sharedWorkspace] iconForFile:[newFilenames objectAtIndex:0]]];
    }
    else {
        [self setImage:[[NSWorkspace sharedWorkspace] iconForFiles:newFilenames]];
    }
    
    // Only keep around the filename if workspace could
    // find an image to go along with it.
    if ([self image] != nil) {
        _filenames = [newFilenames retain];
        [self setTitle:[self titleForFilenames:_filenames]];
    }
}


//-------------------------------------------------------------------
// 	Our options
//-------------------------------------------------------------------

- (BOOL) allowsDoubleClickLaunch
/*"
   Returns YES if double clicking our image will ask NSWorkspace to
   launch our files.
"*/
{
    return _allowDoubleClickLaunch;
}


- (void) setAllowsDoubleClickLaunch:(BOOL)aBool
/*"
    Sets whether double clicking our image will ask NSWorkspace to
    launch our files.
"*/
{
    _allowDoubleClickLaunch = aBool;
}


- (void) launch:(id)sender
/*"
   Not implemented yet.
"*/
{
}


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

- (BOOL) trackMouse:(NSEvent*)theEvent inRect:(NSRect)cellFrame ofView:(NSView*)controlView untilMouseUp:(BOOL)flag; 
/*"
   Extends our superclass's implementation to catch double clicks. If
   #allowsDoubleClickLaunch returns YES and there was a double click
   then we send ourselves a #launch: message.
"*/
{
    // Override mouseDown to check if the icon was double clicked. If so, then
    // launch it from workspace, else let super handle it.

    if ([self allowsDoubleClickLaunch] && [theEvent clickCount] == 2) {
        NSString* filename = [self filename];

        if (filename != nil && [filename cStringLength] > 0) {
            [self launch:self];
            return YES;
        }
    }
		
    return [super trackMouse:theEvent inRect:cellFrame ofView:controlView untilMouseUp:flag];
}

	
- (BOOL) prepareForSourceDrag
/*"
   Puts our filename(s) on the drag pasteboard. Returns YES if we were
   successful and to signify that the drag should continue. If we
   don't represent any filenames then we return NO.
"*/
{
    NSArray* filenames = [self filenames];
    BOOL error = NO;

    if (filenames != nil && [filenames count] > 0) {
        id  dragPB = [self draggingPasteboard];

        [dragPB declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:self];		
        error = ![dragPB setPropertyList:filenames forType:NSFilenamesPboardType];
    }
    
    return !error;
}


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

- (NSString*) titleToDisplay
/*"
   Extends our superclass's implementation to return a temporary
   title if we are the current destination drag cell.
"*/
{
    NSString* titleToDisplay = [super titleToDisplay];
    
    if ([self isDestinationDragInProgress]) {
        titleToDisplay = [[self class] tempTitleToDisplay];
    }

    if (titleToDisplay == nil) {
        titleToDisplay = @"";
    }
    return titleToDisplay;
}


- (void) prepareTextCellForDisplay
/*"
   This method is called just before the text is drawn. If we are currently
   the destination drag cell then we turn our text dark gray by using an
   NSAttributedString.
"*/
{
    [super prepareTextCellForDisplay];

    if ([self isDestinationDragInProgress] ||
        (![self retainsData] && [self isSourceDragInProgress])) {
        NSColor *textColor = [NSColor darkGrayColor];
        NSDictionary *txtDict = [NSDictionary dictionaryWithObjectsAndKeys:textColor, NSForegroundColorAttributeName, nil];
        NSAttributedString *attrStr;
        NSCell* textCell = [self textCell];

        attrStr = [[NSAttributedString alloc] initWithString:[textCell stringValue] attributes:txtDict];
        [textCell setAttributedStringValue:attrStr];
    }
}


//-------------------------------------------------------------------
// 	Our title
//-------------------------------------------------------------------

- (NSString*) titleForFilenames:(NSArray*)someFilenames
/*"
   Given someFilenames we return what our title should be. For
   example if we have multiple filenames our title will be
   something like "x items".
"*/
{
    NSString* aTitle = @"";

    if ([someFilenames count] > 1) {
        aTitle = [NSString stringWithFormat:@"%d items", [someFilenames count]];
    }
    else if ([someFilenames count] == 1) {
        aTitle = [[someFilenames objectAtIndex:0] lastPathComponent];
    }

    return aTitle;
}


//-------------------------------------------------------------------
// 	Archiving
//-------------------------------------------------------------------

- (id) initWithCoder:(NSCoder*)aDecoder
/*"
   Decodes an instance of MiscFileDragCell.
"*/
{
    int version;
    
    [super initWithCoder:aDecoder];

    version = [aDecoder versionForClassName:MiscFileDragCellClassName];
        
    switch (version) {
      case MISC_FILE_DRAG_CELL_VERSION:
          [self setFilenames:[aDecoder decodeObject]];
          [aDecoder decodeValuesOfObjCTypes:"c", &_allowDoubleClickLaunch];
          break;

      default:
          NSLog(@"[%@ %@] - unknown version.", MiscFileDragCellClassName, NSStringFromSelector(_cmd));
        break;
    }

    return self;
}


- (void) encodeWithCoder:(NSCoder*)aCoder
/*"
   Encodes the receiver.
"*/
{
    [super encodeWithCoder:aCoder];

    [aCoder encodeObject:[self filenames]];
    [aCoder encodeValuesOfObjCTypes:"c", &_allowDoubleClickLaunch];
}

@end


@implementation MiscFileDragCell (NSDraggingDestination)

- (unsigned int) draggingEntered:(id <NSDraggingInfo>)sender
/*"
   Grabs the filenames the drag is representing from sender
   and sets our temporary title (displayed in dark gray during
   the drag).                                 .
"*/
{
    NSPasteboard* dragPB = [self draggingPasteboard];

    // This has to be done before we invoke our superclass
    // implementation so it will display our temporary title.
    if ([[dragPB types] containsObject:NSFilenamesPboardType]) {
        NSArray* draggedFilenames;

        draggedFilenames = [[dragPB propertyListForType:NSFilenamesPboardType] retain];
        [[self class] setTempTitleToDisplay:[self titleForFilenames:draggedFilenames]];
        [draggedFilenames release];
    }

    return [super draggingEntered:sender];
}


- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
/*"
   Returns YES if the incoming drag is using the NSFilenamePboardType.
"*/
{
    NSPasteboard* dragPB = [self draggingPasteboard];
    BOOL error = NO;

    error = !([[dragPB types] containsObject:NSFilenamesPboardType]);

    if (!error && [dragPB propertyListForType:NSFilenamesPboardType] == nil) {
        return NO;	
    }

    return YES;
}


- (void) concludeDragOperation:(id <NSDraggingInfo>)sender
/*"
   Takes the filenames from the drag pasteboard and sets them to be
   our new filenames.
"*/
{
    // Take the data from the pasteboard and set the new image.

    NSPasteboard* dragPB = [self draggingPasteboard];
    NSArray* newFilenames;

    // We don't bother to check if there is available data because we
    // we do in performDragOperation:.
    newFilenames = [[dragPB propertyListForType:NSFilenamesPboardType] retain];

    [self setFilenames:newFilenames];

    // Make sure to call super's implementation.
    [super concludeDragOperation:sender];

    [newFilenames release];
}

@end

