/*

 You may freely copy, distribute and reuse the code in this example.
 NeXT disclaims any warranty of any kind, expressed or implied,
 as to its fitness for any particular use.

 CompositeView implements a view with three horizontal, equal-sized areas.
 The left-most area is the "source," the middle area is the "destination,"
 and the right-most area is the "result." CompositeView assures that the
 contents of the result area is always generated by compositing the other
 two areas using the compositing mode set in the setOperator: method.
 It is also possible to change the contents, color, and alpha of the
 source and destination areas; see the methods setSourceColor:, 
 setSourceAlpha:, etc.

 CompositeView also demonstrates some of the drag & drop features of
 OPENSTEP by acting as a destination for colors & images.

 CompositeView written by Bruce Blumberg and Ali Ozer.

 Color support, NXColorPanel, NXColorWells, and NXImage added in 1990.
 Color dragging support added Feb 9, 1992.
 Image dragging support added May 27, 1992.
 Image dragging problem fixed Sep 29, 1992 (for 3.1).
 Conversion to OPENSTEP, Jan 21, 1996.

*/

#import <AppKit/AppKit.h>
#import "CompositeView.h"

@implementation CompositeView

// initWithFrame: creates the view, initializes the rectangles that define the
// three areas described above, and creates the bitmaps used for rendering the
// source and destination bitmaps.

- (id)initWithFrame:(NSRect)rect {

    // Initialize the view

    if (!(self = [super initWithFrame:rect])) return nil;

    // Make rectangles for source, destination and result

    sRect = [self bounds];
    sRect.size.width /= 3.0;
    dRect = sRect;
    dRect.origin.x = sRect.size.width;
    rRect = dRect;
    rRect.origin.x = dRect.origin.x + dRect.size.width;

    // Create source, destination, and result images.

    [(source = [[NSImage allocWithZone:[self zone]] initWithSize:sRect.size]) addRepresentation:[[[NSCustomImageRep alloc] initWithDrawSelector:@selector(drawSource:) delegate:self] autorelease]];
    [source setBackgroundColor:[NSColor clearColor]];
    
    [(destination = [[NSImage allocWithZone:[self zone]] initWithSize:dRect.size]) addRepresentation:[[[NSCustomImageRep alloc] initWithDrawSelector:@selector(drawDestination:) delegate:self] autorelease]];
    [destination setBackgroundColor:[NSColor clearColor]];

    [(result = [[NSImage allocWithZone:[self zone]] initWithSize:dRect.size]) addRepresentation:[[[NSCustomImageRep alloc] initWithDrawSelector:@selector(drawResult:) delegate:self] autorelease]];
    [result setBackgroundColor:[NSColor clearColor]];

    // Set the default operator and source picture. No need to set the default
    // colors; these are read from the .nib file when the outlets to the wells
    // are estanblished.

    operator = NSCompositeCopy;
    sourcePicture = TrianglePicture;

    // Tell the application that alpha should be allowed in the color panel
    // and dragged colors. Most apps do not want to bother with this.

    [NSColor setIgnoresAlpha:NO];

    // Finally, register for dragging colors and files. 

    [self registerForDraggedTypes:[NSArray arrayWithObjects:NSColorPboardType, NSFilenamesPboardType, nil]];

    return self;
}

// Get handles to the wells and read their initial colors.

- (void)setSourceColorWell:(id)anObject {
    sourceColorWell = anObject;
    sourceColor = [[anObject color] copyWithZone:[self zone]]; 
}

- (void)setDestColorWell:(id)anObject {
    destColorWell = anObject;
    destColor = [[anObject color] copyWithZone:[self zone]];
}

- (void)setBackColorWell:(id)anObject {
    backColorWell = anObject;
    backgroundColor = [[anObject color] copyWithZone:[self zone]]; 
}

// drawSource: creates the source image in the  currently locked focus.
// It's specified as a callback to the source NSImage, allowed the image to
// be redrawn whenever needed...

- (void)drawSource:(NSCustomImageRep *)imageRep {	
    [sourceColor set];
    PSnewpath();
    switch (sourcePicture) {

	case TrianglePicture: 
 	    PSmoveto (0.0, 0.0);
	    PSlineto (0.0, sRect.size.height);
	    PSlineto (sRect.size.width, sRect.size.height);
	    break;

	case CirclePicture:
	    PSscale (sRect.size.width, sRect.size.height);
	    PSarc (0.5, 0.5, 0.4, 0.0, 360.0);  // diameter is 80% of area
 	    break;

	case DiamondPicture:
 	    PSmoveto (0.0, sRect.size.height / 2.0);
	    PSlineto (sRect.size.width / 2.0, 0.0);
	    PSlineto (sRect.size.width, sRect.size.height / 2.0);
	    PSlineto (sRect.size.width / 2.0, sRect.size.height);
	    break;

	case HeartPicture:
	    PSscale (sRect.size.width, sRect.size.height);
	    PSmoveto (0.5, 0.5);
	    PScurveto (0.3, 1.0, 0.0, 0.5, 0.5, 0.1);
	    PSmoveto (0.5, 0.5);			
	    PScurveto (0.7, 1.0, 1.0, 0.5, 0.5, 0.1);  
	    break;

	case FlowerPicture:
	    PSscale (sRect.size.width, sRect.size.height);
	    PStranslate (0.5, 0.5);
	    PSmoveto (0.0, 0.0); 
            {int cnt;
	     for (cnt = 0; cnt < 6; cnt++) {
		PSrotate (60.0);
		PScurveto (0.4, 0.5, -0.4, 0.5, 0.0, 0.0);
	     }
	    }
	    break;

	 case CustomPicture:
	    if (!customImage) {
		NSString *fileName = [[NSBundle mainBundle] pathForImageResource:@"DefaultCustomImage.eps"];
		if (fileName) {
                    customImage = [[NSImage allocWithZone:[self zone]] initByReferencingFile:fileName];
                    [customImage setScalesWhenResized:YES];
                    [customImage setSize:rRect.size];
		}
	    }
            [customImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver];
	    break;
 
	default:
	    break;
    }
    PSclosepath();
    PSfill(); 
}

// drawDestination: creates the destination image; like drawSource: is it
// specified as a callback to the destination NSImage

- (void)drawDestination:(NSCustomImageRep *)imageRep {
    [destColor set];
    PSnewpath();
    PSmoveto(dRect.size.width, 0.0);
    PSlineto(dRect.size.width, dRect.size.height);
    PSlineto(0.0, dRect.size.height);
    PSclosepath();
    PSfill(); 
}

// drawResult: creates the resulting image, formed by compositing the
// source image after the destination image with the specified operator. 

- (void)drawResult:(NSCustomImageRep *)imageRep {
    [destination compositeToPoint:NSZeroPoint operation:NSCompositeCopy];
    [source compositeToPoint:NSZeroPoint operation:operator]; 
}
    
// setSourcePicture: allows setting the picture to be drawn in the source
// image. Buttons connected to this method should have tags that are 
// set to the various possible pictures.
//
// After setting the sourcePicture instance variable, setSourcePicture:
// invalidates the images and the view to reflect the new configuration.

- (void)setSourcePicture:(id)sender {
    sourcePicture = [sender selectedTag];
    [source recache];
    [result recache];
    [self setNeedsDisplay:YES]; 
}

// changeCustomImageTo: allows changing the image to be drawn
// in the "Custom" drawing mode

- (BOOL)changeCustomImageTo:(NSImage *)newImage {
    if (newImage && (newImage != customImage)) {
	[newImage setSize:rRect.size];
	[newImage setScalesWhenResized:YES];
	if ([newImage isValid]) {
	    [customImage release];
	    customImage = newImage;
	    if (sourcePicture != CustomPicture) {
		sourcePicture = CustomPicture;
		[sourcePictureMatrix selectCellWithTag:CustomPicture];
	    }
	    [source recache];
	    [result recache];
	    [self setNeedsDisplay:YES];
	    return YES;
	}
    }
    return NO;
}

// Action method to change the custom image; puts up an open panel allowing any
// recognized image file type to be chosen

- (void)changeCustomImage:(id)sender {
    NSOpenPanel *openPanel = [NSOpenPanel openPanel];
    if ([openPanel runModalForTypes:[NSImage imageFileTypes]]) {
	(void)[self changeCustomImageTo:[[NSImage allocWithZone:[self zone]] initByReferencingFile:[openPanel filename]]];
    } 
}

// The following methods change the colors and update
// the source or destination bitmaps and the view to reflect the change.
// They should typically be called by a control capable of returning
// a color (for instance, an NSColorWell).

- (void)changeSourceColor:(id)sender {
    [self changeSourceColorTo:[sender color] andDisplay:YES]; 
}

- (void)changeDestColor:(id)sender {
    [self changeDestColorTo:[sender color] andDisplay:YES]; 
}

- (void)changeBackgroundColor:(id)sender {
    [self changeBackgroundColorTo:[sender color] andDisplay:YES]; 
}

- (void)changeSourceColorTo:(NSColor *)color andDisplay:(BOOL)flag {
    if (![sourceColor isEqual:color]) {
	[sourceColor release];
	sourceColor = [color copyWithZone:[self zone]];
	[source recache];
	[result recache];
	if (flag) [self setNeedsDisplay:YES];
    }
}

- (void)changeDestColorTo:(NSColor *)color andDisplay:(BOOL)flag {
    if (![destColor isEqual:color]) {
	[destColor release];
	destColor = [color copyWithZone:[self zone]];
	[destination recache];
	[result recache];
        if (flag) [self setNeedsDisplay:YES];
    }
}

- (void)changeBackgroundColorTo:(NSColor *)color andDisplay:(BOOL)flag {
    if (![backgroundColor isEqual:color]) {
	[backgroundColor release];
	backgroundColor = [color copyWithZone:[self zone]];
        if (flag) [self setNeedsDisplay:YES];
    }
}

// The operator method returns the operator currently in use.

- (NSCompositingOperation)operator {
    return operator;
}    

// setOperator sets: the operator to be used in the compositing operations
// and updates the view to reflect the change. Note that setOperator needs
// to be connected to a row of buttons.

- (void)setOperator:(id)sender {
    switch ([sender selectedRow]) {
	case 0: operator = NSCompositeCopy;			break;
	case 1: operator = NSCompositeClear;			break;
	case 2: operator = NSCompositeSourceOver; 		break;
	case 3: operator = NSCompositeDestinationOver;		break;
	case 4: operator = NSCompositeSourceIn; 		break;
	case 5: operator = NSCompositeDestinationIn; 		break;
	case 6: operator = NSCompositeSourceOut;		break;
	case 7: operator = NSCompositeDestinationOut;		break;
	case 8: operator = NSCompositeSourceAtop;		break; 
	case 9: operator = NSCompositeDestinationAtop;		break;
	case 10: operator = NSCompositeXOR; 			break;
	case 11: operator = NSCompositePlusDarker;		break;
	case 12: operator = NSCompositePlusLighter;		break;
	default: break;
    }
    [result recache];
    [self setNeedsDisplayInRect:rRect];
}

		
// drawRect: simply redisplays the contents of the view. The source and
// destination rectangles are updated from the bitmaps while the result
// rectangle is created by compositing the two bitmaps.

- (void)drawRect:(NSRect)rect {

    // Erase the whole view

    [backgroundColor set];
    NSRectFill([self bounds]);

    // Color for the frame of the three sections...

    [[NSColor blackColor] set];

    // Draw the source bitmap and then frame it with black

    [source compositeToPoint:sRect.origin operation:NSCompositeSourceOver];
    NSFrameRect(sRect);

    // Draw the destination bitmap and frame it with black 

    [destination compositeToPoint:dRect.origin operation:NSCompositeSourceOver];
    NSFrameRect(dRect);

    // And now for the result image. Frame it with black as well

    [result compositeToPoint:rRect.origin operation:NSCompositeSourceOver];
    NSFrameRect(rRect);
}

// dealloc method to free all the images and colors along with the view.

- (void)dealloc {
    [source release];
    [destination release];
    [result release];
    [sourceColor release];
    [destColor release];
    [backgroundColor release];
    [customImage release];
    [super dealloc];
}


// Code to support dragging...
// This is mostly complicated by the fact that the code wants to demonstrate
// how to dynamically give feedback to the user as the colors are
// dragged (but not dropped). We don't dynamically give feedback when images
// are dragged (as it might take a long time).

- (unsigned int)draggingEntered:(id <NSDraggingInfo>)sender {
    return [self draggingUpdated:sender];
}

// To provide dragging feedback, we change the color of appropriate rect,
// force an immediate display, and then revert the color back. This way
// during the drag operation the colors still retain the original values.

- (unsigned int)draggingUpdated:(id <NSDraggingInfo>)sender {
    if ([sender draggingSourceOperationMask] & NSDragOperationGeneric) {
	NSPasteboard *pboard = [sender draggingPasteboard];
        if ([[pboard types] containsObject:NSColorPboardType]) {	// Color
	    NSColor *sourceColorSave = [[sourceColor retain] autorelease];
	    NSColor *destColorSave = [[destColor retain] autorelease];
	    NSColor *backgroundColorSave = [[backgroundColor retain] autorelease];
	    [self doColorDrag:sender];
	    [self displayIfNeeded];	/* Force a display right now */
	    [self changeSourceColorTo:sourceColorSave andDisplay:NO];	/* Revert without displaying */
	    [self changeDestColorTo:destColorSave andDisplay:NO];
	    [self changeBackgroundColorTo:backgroundColorSave andDisplay:NO];
	    return NSDragOperationGeneric;
	} else if ([NSImage canInitWithPasteboard:pboard]) {	// Image?
	    return NSDragOperationGeneric;
	}
    }
    return NSDragOperationNone;	    
}

- (void)draggingExited:(id <NSDraggingInfo>)sender {
    if ([[[sender draggingPasteboard] types] containsObject:NSColorPboardType]) {	// We need to fix the view up
	[self setNeedsDisplay:YES];
    } 
}

- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
    return ([self draggingUpdated:sender] == NSDragOperationNone) ? NO : YES;
}

- (void)concludeDragOperation:(id <NSDraggingInfo>)sender {
    NSPasteboard *pboard = [sender draggingPasteboard];

    if ([[pboard types] containsObject:NSColorPboardType]) {
	[self doColorDrag:sender];
	[sourceColorWell setColor:sourceColor];
	[destColorWell setColor:destColor];
	[backColorWell setColor:backgroundColor];
    } else {
        (void)[self changeCustomImageTo:[[NSImage allocWithZone:[self zone]] initWithPasteboard:pboard]];
    }
}

- (void)doColorDrag:(id <NSDraggingInfo>)sender {
    NSPoint point = [sender draggingLocation];
    NSColor *color = [NSColor colorFromPasteboard:[sender draggingPasteboard]];

    point = [self convertPoint:point fromView:nil];

    switch ((int)(3 * point.x / NSWidth([self bounds]))) {
	case 0:
            [self changeSourceColorTo:color andDisplay:YES];
	    break;
	case 1:
            [self changeDestColorTo:color andDisplay:YES];
	    break;
	case 2:
            [self changeBackgroundColorTo:color andDisplay:YES];
	    break;
	default:
	    break;	// Shouldn't really happen...
    }
}


@end

