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

#import <AppKit/AppKit.h>
#import "MiscDragCell.h"
#import "MiscDragMatrix.h"

// Private constants
// Some defaults that assume there's going to be a title.
static int MiscDragMatrixDefaultCellWidth = 74;
static int MiscDragMatrixDefaultCellHeight = 74;

// Class ivars
static MiscDragCell* _lastDestinationDragCell = nil;


@implementation MiscDragMatrix

/*"
   MiscDragMatrix is a class that allows for drag cells
   to be used in a matrix. You can create a usable drag
   matrix either by creating a subclass (see MiscFileDragMatrix)
   or just creating an instance of MiscDragMatrix and setting
   the cell class or prototype cell to a class
   or instance that's a concrete subclass of MiscDragCell.
"*/


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

+ (MiscDragCell*) lastDestinationDragCell
/*"
   Used internally to keep track of the last cell that the
   current destination drag was over top of. This is the cell
   that will be forwarded the rest of the drag messages we
   receive.
"*/
{
    return _lastDestinationDragCell;
}


+ (void) setLastDestinationDragCell:(MiscDragCell*)newDestDragCell
/*"
   Internally used method. Sets the drag cell that the
   current destination drag is over top of.
"*/
{
    _lastDestinationDragCell = newDestDragCell;
}


//-------------------------------------------------------------------
// 	Initialization/dellocation
//------------------------------------------------------------------- 

- (id) initWithFrame:(NSRect)frame
/*"
   Calls our superclass's #initWithFrame:, then our #initializeDragTypes
   to allow our view to accept drags. We also set our mode to be
   NSTrackModeMatrix (see NSMatrix for description) and set our default
   cell size to be  74 by 74. Returns self.
"*/
{
    BOOL error = ([super initWithFrame:frame] == nil);

    if (!error) {
        [self initializeDragTypes];
        [self setMode:NSTrackModeMatrix];
        [self setCellSize:NSMakeSize (MiscDragMatrixDefaultCellWidth, MiscDragMatrixDefaultCellHeight)];
    }

    return error ? nil : self;
}


//-------------------------------------------------------------------
// 	Source drag setup
//-------------------------------------------------------------------

- (void) initializeDragTypes
/*"
   Initializes our view to accept destination drag types. We get the
   types from either our first cell (0,0), the prototype cell if there
   is one, or our cell class (it needs to be a subclass of MiscDragCell).
   If we don't have any of the above then we won't accept drags.
"*/
{
    NSArray* acceptableDragTypes = nil;
    NSCell* prototypeCell = [self prototype];
    NSCell*	firstCell = [self cellAtRow:0 column:0];
    MiscDragCell* cellToUse = nil;

    if (firstCell != nil) {
        cellToUse = (MiscDragCell*)firstCell;
    }
    else if (prototypeCell == nil) {
        cellToUse = [[[[self cellClass] alloc] init] autorelease];
    }
    else {
        cellToUse = (MiscDragCell*)[self prototype];
    }

    if ([cellToUse respondsToSelector:@selector(acceptingPasteboardTypes)]) {
        acceptableDragTypes = [cellToUse acceptingPasteboardTypes];
    }

    if ([acceptableDragTypes count] > 0) {
        [self registerForDraggedTypes:acceptableDragTypes];
    }
}


//-------------------------------------------------------------------
// 	Drawing cells
//-------------------------------------------------------------------

- (void) drawCell:(NSCell*)cell
/*"
   NSMatrix's #drawCell: method doesn't seem to do the correct
   thing when faced with a non-opaque cell, so I had to write
   this method.
"*/
{
    int row, column;

    if ([self getRow:&row column:&column ofCell:cell]) {
        NSRect cellRect;

        cellRect = [self cellFrameAtRow:row column:column];
        [self setNeedsDisplayInRect:cellRect];
        [self displayIfNeeded];
    }
}


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

- (MiscDragCell*) _dragCellForDestinationDrag:(id <NSDraggingInfo>)sender
/*"
   Used internally to return the drag cell that's currently under
   the current destination drag session identified by sender. If
   no cell is under the drag, then nil is returned.
"*/
{
    MiscDragCell* currentCell = nil;
    NSPoint dragLocation = [sender draggingLocation];
    int row;
    int column;

    dragLocation = [self convertPoint:dragLocation fromView:nil];
    if ([self getRow:&row column:&column forPoint:dragLocation]) {
        currentCell = [self cellAtRow:row column:column];
    }
    return currentCell;
}


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

- (id) initWithCoder:(NSCoder *)coder
/*"
   Calls our superclass's #initWithCoder:, then our #initializeDragTypes
   to allow our view to accept drags.
"*/
{
    BOOL error = ([super initWithCoder:coder] == nil);

    if (!error) {
        [self initializeDragTypes];
    }

    return error ? nil : self;
}

@end


@implementation MiscDragMatrix (NSDraggingDestination)

- (unsigned int) draggingEntered:(id <NSDraggingInfo>)sender
/*"
   Finds the drag cell under the current destination drag, passes
   it the #draggingEntered: message.
"*/
{
    MiscDragCell* currentCell = [self _dragCellForDestinationDrag:sender];

    [[self class] setLastDestinationDragCell:currentCell];
    return [currentCell draggingEntered:sender];
}


- (unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender
/*"
   Makes sure that the destination drag cell is still the same as
   it was when our #draggingEntered: method was called. If it is
   then we pass #draggingUpdated: to the cell. If not, we send the
   last drag cell a #draggingExited: message and the newly
   affected cell a #draggingEntered: message.
"*/
{
    MiscDragCell* prevCell = [[self class] lastDestinationDragCell];
    MiscDragCell* currentCell = [self _dragCellForDestinationDrag:sender];
    unsigned int retVal = 0;
    
    if (prevCell == currentCell) {
        retVal = [currentCell draggingUpdated:sender];
    }
    else {
        [prevCell draggingExited:sender];
        [[self class] setLastDestinationDragCell:currentCell];
        retVal = [currentCell draggingEntered:sender];
    }
    return retVal;
}

- (void) draggingExited:(id <NSDraggingInfo>)sender
/*"
   Passes the #draggingExited: message on to the last cell
   the drag was over.
"*/
{
    [[[self class] lastDestinationDragCell] draggingExited:sender];
}

- (BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender
/*"
    Passes the #prepareForDragOperation: message on to the last cell
    the drag was over.
"*/
{
    return [[[self class] lastDestinationDragCell] prepareForDragOperation:sender];
}

- (BOOL) performDragOperation:(id <NSDraggingInfo>)sender
/*"
   Passes the #performDragOperation: message on to the last cell
   the drag was over.
"*/
{
    return [[[self class] lastDestinationDragCell] performDragOperation:sender];
}

- (void) concludeDragOperation:(id <NSDraggingInfo>)sender
/*"
   Passes the #concludeDragOperation: message on to the last cell
   the drag was over.
"*/
{
    return [[[self class] lastDestinationDragCell] concludeDragOperation:sender];
}

@end

