/**************************************************************************
 * CLASS:		MiscDragView
 *
 *	See the header file for more information on this class.
 *
 * 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.
 **************************************************************************/
 
//#import <misckit/MiscString.h>
#import <misckit/MiscDragView.h>


@implementation MiscDragView

- initFrame: (const NXRect *)frameRect
{
    [super initFrame: frameRect];

	[self setAllowSourceDragging: YES];
	[self setAllowDestinationDragging: YES];
	[self setAcceptForeignDrag: YES];
	[self setAcceptLocalDrag: YES];
	[self setAcceptSelfDrag: YES];
	[self setRetainData: YES];

	[self setBorderType: NX_BEZEL];	
	dragImage = nil;
	theImage = nil;	
	[self setDelegate: nil];

    return self;
}



- awake
{
	[super awake];

	dragImage = nil;
	
	return self;
}



- free
{
	if (theImage != nil)
		[theImage free];
		
	return [super free];
}



- setImage: (NXImage *)anImage
{		
	if ([self image] != nil)
		[theImage free];
    theImage = anImage;
	
	[theImage getSize: &imageSize];
	[theImage setScalable: YES];
	[theImage setDataRetained: YES];
	
	[self display];
    return self;
}



// Though the docs for NXImage say that initFromFile: does not need an
// absolute pathname, I would recommend it. I had problems with it not 
// finding images even when they were in the app wrapper

- setImageByFilename: (char *)aFilename
{
	[self setImage: [ [NXImage alloc] initFromFile: aFilename] ];
	return self;
}



- (NXImage *)image
{
	return theImage;
}




// Border Type can be one of NX_BEZEL, NX_GROOVE, NX_LINE, or NX_NOBORDER,
// same as the appkit's Box.

- setBorderType: (int)aType
{
	border = aType;
	[self display];
	return self;
}



- (int)borderType
{
	return border;
}



// Options that could apply to any subclassed DragView

- setAllowSourceDragging: (BOOL)aBool
{
	allowSourceDragging = aBool;
	
	return self;
}



- (BOOL)allowSourceDragging
{
	return allowSourceDragging;
}



- setAllowDestinationDragging: (BOOL)aBool
{
	allowDestinationDragging = aBool;
	return self;
}



- (BOOL)allowDestinationDragging
{
	return allowDestinationDragging;
}



- setAcceptForeignDrag: (BOOL)aBool
{
	acceptForeignDrag = aBool;
	return self;
}



- (BOOL)acceptForeignDrag
{
	return acceptForeignDrag;
}



- setAcceptLocalDrag: (BOOL)aBool
{
	acceptLocalDrag = aBool;
	return self;
}



- (BOOL)acceptLocalDrag
{
	return acceptLocalDrag;
}



- setAcceptSelfDrag: (BOOL)aBool
{
	acceptSelfDrag = aBool;
	return self;
}



- (BOOL)acceptSelfDrag
{
	return acceptSelfDrag;
}



- setRetainData: (BOOL)aBool
{
	retainData = aBool;
	return self;
}



- (BOOL)retainData
{
	return retainData;
}



/*-------------------- methods to be the source of a dragging operation */

- mouseDown: (NXEvent *)theEvent
{
  NXPoint  zero = {0.0, 0.0};
  NXPoint  offset = theEvent->location;
  BOOL slide = YES;
  id  dragPB = [Pasteboard newName: NXDragPboard];
  
 	if ([self allowSourceDragging] == NO)
		return [super mouseDown: theEvent]; 

	// if the overridden setupForSourceDrag returns yes continue
	
	if ([self setupForSourceDrag])
	{	
  		[self convertPoint: &offset fromView: nil]; 

		// give the chance to change the offset and dragImage start point
		
		[self calculateDragPoint: &offset andOffset: &zero];
		
		if (dragImage != nil)
		{			
			if (![self retainData])
			{
				slide = NO;
				
				// setting theImage to nil will make it disappear, giving
				// the impression that the data was not retained
				
				if (dragImage == theImage)
					theImage = nil;
				else
					[theImage free];
			
				[self display];
			 }
			 
			// send out the delegate message
			
			[self sourceDragInitiated: self];
							
			// begin the drag session

    		[NXApp preventWindowOrdering];			
			
			[self dragImage: dragImage at: &offset offset: &zero 
					event: theEvent	pasteboard: dragPB 
					source: self slideBack: slide];			
		 }
	 }
	  	
    return self;
}



// 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).

- calculateDragPoint: (NXPoint *)dragPoint andOffset: (NXPoint *)offset
{
	return self;
}



// A method that should be overridden in the subclass to put the information
// on the desired pasteboard and select an image to drag.

- (BOOL)setupForSourceDrag
{
	return NO;
}



// The following are methods from the NXSourceDraggingProtocol that are
// here so they are easy to lookup.

- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
{
    return NX_DragOperationAll;
}



- draggedImage:(NXImage *)image beganAt:(NXPoint *)screenPoint
{
	return self;
}



// Sends a "sourceDragFinished:" to the delegate if it responds.

- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint 
		deposited:(BOOL)flag
{
	[self sourceDragFinished: flag];

	// clean up a little. Since the drag is done, free the dragImage 
	// unless it is just a pointer to theImage (the image in the view).
	
	if (![self retainData])
		[dragImage free];
	else
		if (theImage == dragImage)
			dragImage = nil;
		else
			[dragImage free];
			
	return self;
}



/*-------------------- methods to be the destination of a dragging operation */

- (NXDragOperation)draggingEntered: sender
{
	if ([self allowDestinationDragging] == NO)
		return NX_DragOperationNone;
		
 	if ([self acceptSelfDrag] == NO && [sender draggingSource] == self)
		return NX_DragOperationNone;
		
	if ([self acceptForeignDrag] == NO && [sender isDraggingSourceLocal] == NO)
		return NX_DragOperationNone;
		
    if ([self acceptLocalDrag] == NO && [sender isDraggingSourceLocal] == YES)
		return NX_DragOperationNone;
	
	// Send a "destinationDragInitiated:" to the delegate
	
	[self destinationDragInitiated: self];
	
	return NX_DragOperationCopy;
}



- (NXDragOperation)draggingUpdated: sender
{	
	return [super draggingUpdated: sender]; 
}



// Destination drag came to an end, but was not successful, so send a NO
// to the delegate

- draggingExited:sender
{
	[self destinationDragFinished: NO];
    return self;
}



- (BOOL)prepareForDragOperation:sender
{
    return YES;
}



- (BOOL)performDragOperation:sender
{	
    return YES;
}



// Send the delegate a "destinationDragFinished" message

- concludeDragOperation:sender
{
	[self destinationDragFinished: YES];
    return self;
}



/*-------------------- methods to display, and other useful stuff */

- (BOOL)acceptsFirstMouse
{
    return YES;
}



- (BOOL)shouldDelayWindowOrderingForEvent:(NXEvent *)theEvent
{
    return YES;
}

 
 
// delegate methods
 
- delegate
{
    return delegate;
}



- setDelegate: (id)theDelegate
{
    delegate = theDelegate;
	return self;
}



// Sent from "mouseDown" just before the dragging starts to take place

- sourceDragInitiated: view
{
	if ([delegate respondsTo: @selector(sourceDragInitiated:)])
		[delegate sourceDragInitiated: view];
	return self;
}



// Sent when "draggedImage: endedAt: screenPoint: deposited:" is given 
// control and success is whether the image was deposited in another
// view somewhere

- sourceDragFinished: (BOOL)success
{
	if ([delegate respondsTo: @selector(sourceDragFinished:)])
		[delegate sourceDragFinished: success];
	return self;
}



// Sent when "draggingEntered:" is given control

- destinationDragInitiated: view
{
	if ([delegate respondsTo: @selector(destinationDragInitiated:)])
		[delegate destinationDragInitiated: view];
	return self;
}



// Sent when "concludeDragOperation:" is given control

- destinationDragFinished: (BOOL)success
{
    if ([delegate respondsTo: @selector(destinationDragFinished:)])
		[delegate destinationDragFinished: success];
	return self;
}



// Draw the border, background color and the image if there is one.
// I tried to add the scaling code to sizeTo:: but it did not seem
// that it was called every time the view was resized (almost all the
// time, but not good enough). So to be safe for now I lumped it all together.

- drawSelf: (const NXRect *)rects :(int)rectCount;
{
  NXPoint  offset;
  NXRect  imageRect;

  
	switch (border)
	{
	    case NX_GROOVE:
	  	  NXDrawGroove (&bounds, rects);
		  break;
	    case NX_BEZEL:
	   	  NXDrawGrayBezel(&bounds, rects);
		  break;
	    case NX_LINE:
	  	  NXSetColor (NX_COLORBLACK);
		  NXFrameRect (&bounds);
		  break;
	  	case NX_NOBORDER:
		  NXSetColor (NX_COLORLTGRAY);
		  NXRectFill (&bounds);
	  	  break;
	  }
	 
	// set so the background color will not wipe the border
	
    imageRect.origin.x = bounds.origin.x + 2;
   	imageRect.origin.y = bounds.origin.y + 2;
    imageRect.size.width = bounds.size.width - 4;
    imageRect.size.height = bounds.size.height - 4;
	
	// if there is no image, then no reason to continue
	
	if ([self image] == nil)
		return self;
	
	// use different offsets depending on if image scaled or not
			
	if (imageSize.width > bounds.size.width ||
			imageSize.height > bounds.size.height)
	{  
	  NXSize  scaledSize;
	  double xscale, yscale, scale;

		// scale the image and keep aspect ratio
		
	 	xscale = bounds.size.width / imageSize.width;
		yscale = bounds.size.height / imageSize.height;
		scale = ((xscale < yscale) ? xscale : yscale);
		scaledSize.width = (imageSize.width * scale) - 2.0; 
		scaledSize.height = (imageSize.height * scale) - 2.0;
		
		offset.x = (bounds.size.width - scaledSize.width) / 2;
		offset.y = (bounds.size.height - scaledSize.height) / 2;
		
		if (xscale < yscale)		// so the image is not right on the edge
			offset.x+=2.0; 
		else
			offset.y+=1.0;

		scaledSize.width -= 3.0;	// so when we move the offset,..
		scaledSize.height -= 3.0;	// image will be basically centered
		
		[theImage setSize: &scaledSize];
	 }
	else
	{ 
		// no scaling needed
		
		offset = imageRect.origin;
		offset.x += (imageRect.size.width - imageSize.width) / 2.0;
		offset.y += (imageRect.size.height - imageSize.height) / 2.0;
		[theImage setSize: &imageSize];
	 }

	[theImage composite: NX_SOVER toPoint: &offset];
    return self;
}



// Archiving methods

- read: (NXTypedStream *)stream
{
	[super read: stream];
	
	theImage = NXReadObject (stream);
	NXReadSize (stream, &imageSize);
	NXReadTypes (stream, "i", &border);	
	
	delegate = NXReadObject (stream);
	NXReadTypes (stream, "c", &allowSourceDragging);
	NXReadTypes (stream, "c", &allowDestinationDragging);
	NXReadTypes (stream, "cc", &acceptForeignDrag, &acceptLocalDrag);
	NXReadTypes (stream, "cc", &acceptSelfDrag, &retainData);
	
	return self;
}



- write: (NXTypedStream *)stream
{
	[super write: stream];
	
	NXWriteObject (stream, theImage);
	NXWriteSize (stream, &imageSize);	
	NXWriteTypes (stream, "i", &border);

	NXWriteObjectReference (stream, delegate);
	NXWriteTypes (stream, "c", &allowSourceDragging);
	NXWriteTypes (stream, "c", &allowDestinationDragging);
	NXWriteTypes (stream, "cc", &acceptForeignDrag, &acceptLocalDrag);
	NXWriteTypes (stream, "cc", &acceptSelfDrag, &retainData);
	
	return self;
}



- (const char *)getInspectorClassName
{
	return "MiscDVInspector";
}

 
@end
