/*	MiscRubberband.m

	Copyright 1996 Slugg Jello.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Converted to OpenStep, August 1996, Uwe Hoffmann.
*/


#import "MiscRubberband.h"
#import "Miscrubber.h" 
#include <math.h>

#define MISC_RUBBERBAND_WINDOW_WIDTH	1.0

@interface MiscRubberband (PrivateMethods)

- (void)_internalCorrectRect:(NSRect *)rect;
- (void)_internalSetPosition;

@end

@implementation MiscRubberband
/*"An instance of this class provides the functionality of stretching a selection
rectangle for an associated view. When a mouseDown event occurred in the view, the view
can pass the event with #doStretch: to a rubberband, which further processes mouseDragged events and draws
a corresponding rectangle until a mouseUp event occurs. The view can then query the
rubberband for the selection rectangle with #currentRect.

All the coordinates queried or set in the rubberband are in the coordinate system of the associated view.
The rectangle returned by #currentRect can be inclusive or exclusive the rubberband width. This is set
with the method #setInclusive: and queried with #inclusive.

The rubberband stretches the rectangle in two ways: with the mouseDown location (the pivot) at the corner
or at the center of the rectangle. You can query the pivot type with #pivot and set it with #setPivot:.

The stretching area can be bound to an rectangle with #setBounds:."*/


+ rubberbandWithView:(NSView *)aView
/*"Creates, initializes and returns a MiscRubberband instance associated with aView."*/
{
	return [[[MiscRubberband alloc] initWithView:aView] autorelease];
}

- initWithView:(NSView *)aView
/*"Initializes a newly created instance of MiscRubberband with aView. This is the designated initializer of this class."*/
{
	NSParameterAssert(aView);	
	[super init];
	view = aView;
	boundsRect = [view convertRect:[view visibleRect] toView:nil];
	currentRect = NSZeroRect;
	pivot = MiscCornerPivot;
	flags.inclusive = YES;
	flags.usesBounds = YES;
	return self;
}

- (void)setView:(NSView *)aView
/*"Sets the associated view of the receiver to aView."*/
{
	NSParameterAssert(aView);
	view = aView;
	boundsRect = [view convertRect:[view visibleRect] toView:nil];
}

- (NSView *)view
/*"Returns the associated view of the receiver."*/
{
	return view;
}

- (NSRect)bounds
/*"Returns the stretching bounds rectangle of the receiver."*/
{
	return [view convertRect:boundsRect fromView:nil];
}

- (void)doStretch:(NSEvent *)event
/*"Performs the stretching beginning with event and ending when the first mouseUp event occurs."*/
{
	NSEvent *nextEvent;
	NSPoint mouseLocation, p, pivotPt;
	NSRect tRect;
	NSWindow *window = [view window];
			 
	currentRect.origin = [event locationInWindow];
	currentRect.size.width = currentRect.size.height = 0.0;	
        if(flags.usesBounds && ![view mouse:currentRect.origin inRect:boundsRect])
		return;
	mouseLocation = currentRect.origin;
	if(pivot == MiscCenterPivot)
		pivotPt = currentRect.origin;
	MiscPSWInitRubberband();
	[self _internalSetPosition];
	MiscPSWMakeVisible(YES);
	nextEvent = [window nextEventMatchingMask:NSLeftMouseUpMask|NSLeftMouseDownMask|NSLeftMouseDraggedMask];	
	tRect = currentRect;
	while([nextEvent type] != NSLeftMouseUp){
		p = [nextEvent locationInWindow];
		if(pivot == MiscCenterPivot){
			tRect.size.width += (p.x - mouseLocation.x) * 2;
			tRect.size.height += (p.y - mouseLocation.y) * 2;
			tRect.origin.x = pivotPt.x - tRect.size.width / 2;
			tRect.origin.y = pivotPt.y - tRect.size.height / 2;
		} else {
			tRect.size.width += p.x - mouseLocation.x;
			tRect.size.height += p.y - mouseLocation.y;
		}
		currentRect = tRect;
		[self _internalCorrectRect:&currentRect];
		if(flags.usesBounds)
			currentRect = NSIntersectionRect(boundsRect,currentRect);
		currentRect = NSIntegralRect(currentRect);
		mouseLocation = p;
		[self _internalSetPosition];
		nextEvent = [window nextEventMatchingMask:NSLeftMouseUpMask|NSLeftMouseDownMask|NSLeftMouseDraggedMask];
	}
	MiscPSWMakeVisible(NO);
}

- (NSRect)currentRect
/*"Returns the rectangle resulted from a stretching."*/
{
	NSRect r;

	r = currentRect;
	if(r.size.width == 0 && r.size.height == 0)
		return r;
	if(!flags.inclusive){
		r.origin.x += MISC_RUBBERBAND_WINDOW_WIDTH;
		r.origin.y += MISC_RUBBERBAND_WINDOW_WIDTH;
		r.size.width -= 2 * MISC_RUBBERBAND_WINDOW_WIDTH;
		r.size.height -= 2 * MISC_RUBBERBAND_WINDOW_WIDTH;
	}
	return [view convertRect:r fromView:nil];	
}

- (BOOL)inclusive
/*"Returns whether the rectangle returned by #currentRect is inclusive the rubberband width."*/
{
	return flags.inclusive;
}

- (void)setInclusive:(BOOL)aBool
/*"Sets the inclusive flag of the receiver to aBool. This flag indicates 
whether the rectangle returned by #currentRect is inclusive the rubberband width."*/
{
	flags.inclusive = aBool;
}

- (BOOL)usesBounds
/*"Returns YES if the stretching is bound."*/
{
	return flags.usesBounds;
}

- (void)setUsesBounds:(BOOL)aBool
/*"Sets the inclusive flag of the receiver to aBool. This flag indicates 
whether the stretching is bound."*/
{
	flags.usesBounds = aBool;
}

- (void)setBounds:(NSRect)bounds
/*"Sets the bounding rectangle of the receiver to bounds."*/
{
	boundsRect = [view convertRect:bounds toView:nil];
}

- (void)setCurrentRectToBounds
/*"Sets the current selection rectangle to be the bounding rectangle of the receiver."*/
{
	boundsRect = currentRect;
}

- (void)setPivot:(MiscPivotType)aPivot
/*"Sets the pivot type of the receiver to aPivot."*/
{
	pivot = aPivot;
}

- (MiscPivotType)pivot
/*"Returns the pivot type of the receiver."*/
{
	return pivot;
}
 
@end

@implementation MiscRubberband (PrivateMethods)

- (void)_internalCorrectRect:(NSRect *)rect
{
	if (rect->size.width < 0.0){
		rect->origin.x += rect->size.width;
		rect->size.width = -rect->size.width;
	}
	if (rect->size.height < 0.0){
		rect->origin.y += rect->size.height;
		rect->size.height = -rect->size.height;
	}
}

- (void)_internalSetPosition
/* Position and draw rubberband.  Assumes currentRect is set and rubberband
	windows are initialized.  Also assumes currentRect is 'corrected'. */
{
	struct {
		NSRect top,left,bottom,right;
	} points;
	
	NSRect tRect, screenRect = currentRect;
	
	screenRect.origin = [[view window] convertBaseToScreen:screenRect.origin];
		
	/**** set size of each rubberband side ****/
	
	/* left */
	tRect = screenRect;
	tRect.size.width = MISC_RUBBERBAND_WINDOW_WIDTH;
	tRect.size.height = MAX(MISC_RUBBERBAND_WINDOW_WIDTH,screenRect.size.height);
	points.left = tRect;
	
	/* right */
	tRect.origin.x += screenRect.size.width - 1.0;
	points.right = tRect;
	
	/* bottom */
	tRect = screenRect;
	tRect.origin.x += 1.0;
	tRect.size.width = MAX(MISC_RUBBERBAND_WINDOW_WIDTH,screenRect.size.width - 1.0);
	tRect.size.height = MISC_RUBBERBAND_WINDOW_WIDTH;
	points.bottom = tRect;
	
	/* top */
	tRect.origin.y += screenRect.size.height - 1.0;
	points.top = tRect;
	
	/* this'll erase old one and draw new one in new position */
	MiscPSWPositionRect((float *)&points);

}

@end
