/*	MiscDiagramTree.m

	Copyright 1996 Uwe Hoffmann.

	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.

	Version 2 (August 1996)
*/

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

#import "MiscDiagramTree.h"
#import "MiscTreeStyle.h"
#import "MiscNodeStyle.h"
#import "MiscTreeDiagram.h"
#import "MiscUserPath.h"
#import "MiscHitPath.h"
// There's gotta be a better way to grab this header!
#import "../derived_src/Misccontrol.h"
#import "Miscending.h"

extern const float miscTD_LargeNumberForText;

static float controlPts[8],controlX,controlY,controlFontSize;
static char controlChars[5];
static char controlPrimChars[5];
static NSString *controlFontname = @"MiscTreeDiagramControlPoints";
static MiscUserPath *arrowEnding, *doubleEnding, *circleEnding;
static NSTextView *drawText;
static NSImage *collapseImage = nil;

@interface MiscDiagramTree(PrivateMethods)

- (void)_internalFillLinesIn:(MiscUserPath *)linesPath;

@end

@implementation MiscDiagramTree
/*"	The MiscDiagramTree class extends the tree data structure represented
	by the MiscLayoutTree class with drawing capabilities. An instance of
	this class can draw itself in a MiscTreeDiagramView instance. It is 
	defined by a shape (an instance of MiscDiagramShape), a label and an image.
	Its appearance is also formed by two defining styles: its own node style
	(an instance of MiscNodeStyle) and the style of the tree diagram it belongs
	to (an instance of MiscTreeStyle).
	
	The drawing has been split into several methods for efficiency reasons. It
	enables the drawing of all shadows, then all fills then all outlines... etc. 
	This minimizes the changes that have to be set in the postscript drawing state.
	
	In addition to the basic drawing there's drawing functionality for visual feedback
	on the state of the MiscDiagramTree instance, i.e. if it's selected, collapsed,
	has attachments and so on. Also there are methods for interactive drawing when
	the instance is in a resizing event in a view.
	
	Finding out the bounding rectangle of an MiscDiagramTree instance including the 
	selection handles, the attachments handles, considering the linewidth and so on...
	is done with the methods #nodeDrawBounds and #branchDrawBounds.
"*/	    

+ (void)initialize
{
	int i;
	
	if(self == [MiscDiagramTree class]){
       	 	MiscPSWDefineFont([controlFontname cString]);
		controlFontSize = 8.0;
		controlPts[6] = 0;
		controlPts[7] = 0;
		controlChars[4] = 0;
		controlPrimChars[4] = 0;
		for(i = 0;i < 4;i++){
			controlChars[i] = 'a';
			controlPrimChars[i] = 'e';
		}
                arrowEnding = [[MiscUserPath alloc] init];
                [arrowEnding moveto:0 :0];
                [arrowEnding rlineto:10 :5];
                [arrowEnding rlineto:-3 :-5];
                [arrowEnding rlineto:3 :-5];
                [arrowEnding closepath];
                doubleEnding = [[MiscUserPath alloc] init];
                [doubleEnding moveto:0 :0];
                [doubleEnding rlineto:5 :5];
                [doubleEnding rlineto:-2 :-5];
                [doubleEnding rlineto:7 :5];
                [doubleEnding rlineto:-2 :-5];
                [doubleEnding rlineto:2 :-5];
                [doubleEnding rlineto:-7 :5];
                [doubleEnding rlineto:2 :-5];
                [doubleEnding closepath];
                circleEnding = [[MiscUserPath alloc] init];
                [circleEnding moveto:6 :0];
                [circleEnding arc:3 :0 :3 :0 :360];
                drawText = [[NSTextView alloc] init];
                [[drawText textContainer] setWidthTracksTextView:NO];
                [[drawText textContainer] setHeightTracksTextView:NO];
                [drawText setHorizontallyResizable:NO];
                [drawText setVerticallyResizable:YES];
                [drawText setDrawsBackground:NO];
                [drawText setRichText:YES];
                [drawText setEditable:NO];
                [drawText setSelectable:NO];
	}
}
		
+ tree
/*"Creates, initializes and returns a MiscDiagramTree instance."*/
{
	return [[[self alloc] init] autorelease];
} 

+ treeWithLabel:(NSAttributedString *)aLabel shapeType:(MiscShapeType)aShapeType
/*"Creates, initializes and returns a MiscDiagramTree instance with label aLabel 
and shape type aShapeType."*/
{
	return [[[self alloc] initWithLabel:aLabel shapeType:aShapeType] autorelease];
} 

+ treeWithSize:(NSSize)aSize shapeType:(MiscShapeType)aShapeType
/*"Creates, initializes and returns a MiscDiagramTree instance with size aSize
and shape type aShapeType."*/
{
        return [[[self alloc] initWithSize:aSize shapeType:aShapeType] autorelease];
} 

- initWithLabel:(NSAttributedString *)aLabel shapeType:(MiscShapeType)aShapeType
/*"Initializes a newly created instance of MiscDiagramTree with label aLabel 
and shape type aShapeType. It calculates the size to fit the label."*/
{  
	NSSize s = {30,10};
	
	[self initWithSize:s shapeType:aShapeType];
	[style setAutomaticResize:YES];
	[self setLabel:aLabel];
	[style setAutomaticResize:NO];	
   	return self;
}

- initWithSize:(NSSize)aSize shapeType:(MiscShapeType)aShapeType
/*"Initializes a newly created instance of MiscDiagramTree with size aSize 
and shape type aShapeType.
This is the designated initializer of this class."*/
{  
	NSRect r;
	
	self = [super initWithSize:aSize];
	label = nil;
	r.origin.x = r.origin.y = 0;
	r.size = aSize;
	shape = [[MiscDiagramShape allocWithZone:[self zone]] initWithShapeType:aShapeType bounds:r];
	selected = NO;
	style = [[MiscNodeStyle alloc] init]; 
	resizeFlag = 0;	
   	return self;
}

- init
{
    	NSSize s = {50,30};
        
   	return [self initWithSize:s shapeType:MiscRectangleShapeType];  	
}
   
- (void)dealloc
{
    	if(label)
		[label release];
	[shape release];
        [style release];
   	return [super dealloc];
}

- copyNodeWithZone:(NSZone *)zone
{
	MiscDiagramTree *theCopy;
	
	theCopy = [super copyNodeWithZone:zone];
	theCopy->shape = [shape copyWithZone:zone];
	theCopy->label = [label copyWithZone:zone];
	theCopy->style = [style copyWithZone:zone];
	return theCopy;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
	[super encodeWithCoder:coder];
	[coder encodeObject:shape];
	[coder encodeObject:label];
	[coder encodeObject:style];
	[coder encodeConditionalObject:diagram];
}

- initWithCoder:(NSCoder *)coder
{
	self = [super initWithCoder:coder];
	shape = [[coder decodeObject] copyWithZone:[self zone]];
        label = [[coder decodeObject] copyWithZone:[self zone]];
        style = [[coder decodeObject] copyWithZone:[self zone]];
	diagram = [coder decodeObject];
	selected = NO;
	resizeFlag = 0;
	return self;
}

- (NSRect)nodeDrawBounds
/*"	Returns the bounds of the receiver taking into consideration
	style properties like linewidth and the extension of the
	various handles and signs like the selection handles, the
	attachments handles and the collapsed sign."*/
{
	NSRect b;
	float delta;
	
	delta = MAX(2, [style linewidth] / 0.17);	// initial delta for avoiding roundoff errors
	if(selected)
		delta = MAX(delta, controlFontSize / 2);
        if(([style childEnding] != MiscNoEnding && [self parent]) || ([style parentEnding] != MiscNoEnding && [self child]))
            	delta = MAX(delta, 10);
        if([self isCollapsed])
            	delta = MAX(delta, 20);
	b = [super nodeBounds];
	b.origin.x -= delta;
	b.origin.y -= delta;
        if([[diagram treeStyle] shadow]){
            b.size.width += 2 * delta + 5;
            b.size.height += 2 * delta + 5;
        } else {
            b.size.width += 2 * delta;
            b.size.height += 2 * delta;
        }
	return b;
}

- (NSRect)textBounds
/*"	Returns the text bounds of the receiver. The label text of the receiver is clipped to this bounds."*/
{
        return [shape innerBounds];
}

- (NSRect)branchDrawBounds
/*"	Returns the bounds of the tree rooted in the receiver 
	taking into consideration
	style properties like linewidth and the extension of the
	various handles and signs like the selection handles, the
	attachments handles and the collapsed sign."*/
{
	NSRect b;
	float delta = 10;
	
	b = [super branchBounds];
	b.origin.x -= delta;
	b.origin.y -= delta;
        if([[diagram treeStyle] shadow]){
            b.size.width += 2 * delta + 5;
            b.size.height += 2 * delta + 5;
        } else {
            b.size.width += 2 * delta;
            b.size.height += 2 * delta;
        }
	return b;
}

- (NSPoint)endPoint
/*"Returns the endpoint on the receiver's side of the lines connecting the receiver
with its parent and children. In this implementation it's the middle
point of the receiver's shape. You can overwrite this method to provide some other
endpoint."*/
{
    	NSPoint rp;

        rp.x = [self pos].x + [self size].width / 2;
        rp.y = [self pos].y + [self size].height / 2;
        return rp;
}

- (NSPoint)parentEndPoint
/*"Returns the bending point on the receiver's side of the line connecting the receiver
with its children when the line type is %MiscBendLineType (see the #MiscTreeStyle class).
In this implementation it's calculated from the %bendingFactor and the %distanceToParent
parameters (see the #MiscTreeStyle and #MiscLayoutTree classes).
You can overwrite this method to provide some other bending point."*/
{
        NSPoint rp;
        float m = ([self distanceToParent] + [self border]) * (1 - [[diagram treeStyle] bendingFactor]) / 2;

        if([self layoutType] == MiscHorizontalTreeType){
            	rp.x = [self pos].x + [self size].width + m;
             	rp.y = [self pos].y + [self size].height / 2;
        } else {
            	rp.x = [self pos].x + [self size].width / 2;
                rp.y = [self pos].y + [self size].height + m;
        }       
        return rp;
}

- (NSPoint)childEndPoint
/*"Returns the bending point on the receiver's side of the line connecting the receiver
with its parent when the line type is %MiscBendLineType (see the #MiscTreeStyle class).
In this implementation it's calculated from the %bendingFactor and the %distanceToParent
parameters (see the #MiscTreeStyle and #MiscLayoutTree classes).
You can overwrite this method to provide some other bending point."*/
{
        NSPoint rp;
        float m = ([self distanceToParent] + [self border]) * (1 - [[diagram treeStyle] bendingFactor]) / 2;

        if([self layoutType] == MiscHorizontalTreeType){
                rp.x = [self pos].x - m;
                rp.y = [self pos].y + [self size].height / 2;
        } else {
                rp.x = [self pos].x + [self size].width / 2;
                rp.y = [self pos].y - m;
        }
        return rp;
}

- (void)layoutNode
{
	[shape moveTo:[self pos]];
	[shape sizeTo:[self size]];
}

- (void)drawShadow:(MiscDrawState *)aState
/*"	Draws the shadow of the receiver. There should be a view with locked focus
	and the shadow color already set when this method is called.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results."*/ 
{
    	[shape drawShadowWithDelta:NSMakePoint(5,5)];
	return;
}

- (void)drawOutline:(MiscDrawState *)aState knobs:(BOOL)knobs viewScale:(float)viewScale
/*"	Draws the outline of the receiver. There should be a view with locked focus
	when this method is called.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results.
	The method compares the setting it has to make to the postscript graphic state
	with the values in aState. aState should have valid values reflecting the
	current postscript graphic state.
	The changes to the postscript graphic state are written back in aState.
	The method draws the selection knobs if the receiver is selected and aBool
	has the value YES. The value of viewScale gives the scaling factor of the
	current postscript graphic state. The method calculates the size of the
	knobs such that it appears constant onscreen regardless of the scaling factor."*/
{
	float linewidth;
	NSColor *outlineColor;
	
	if(resizeFlag)
		return;
	if([style outline]){
		linewidth = [style linewidth];
		outlineColor = [style outlineColor];
		if(![aState->color isEqual:outlineColor]){
                    	if(aState->color)
                            	[aState->color release];
			aState->color = [outlineColor retain];
			[outlineColor set];
		}
		if(aState->linewidth != linewidth){
			aState->linewidth = linewidth;
			PSsetlinewidth(linewidth);
		}
		[shape drawOutline];
	}
	if(selected && knobs){ 
		PSselectfont([controlFontname cString], MIN(controlFontSize / viewScale, controlFontSize));
		aState->font = nil;
		if(![aState->color isEqual:[NSColor blackColor]]){
                    	if(aState->color)
                                  [aState->color release];
			aState->color = [[NSColor blackColor] retain];
			[aState->color set];
		}
		controlX = [self pos].x;
		controlY = [self pos].y;
		controlPts[0] = [self size].width;
		controlPts[1] = 0;
		controlPts[2] = 0; 
		controlPts[3] = [self size].height;
		controlPts[4] = -controlPts[0];
		controlPts[5] = 0;
		MiscPSWDrawControlPoints(controlX,controlY,controlPts,8,controlChars);
		if(![aState->color isEqual:[NSColor darkGrayColor]]){
                    	if(aState->color)
                                   [aState->color release];
			aState->color = [[NSColor darkGrayColor] retain];
			[aState->color set];
		}
		MiscPSWDrawControlPoints(controlX,controlY,controlPts,8,controlPrimChars);
	}
}

- (void)drawFill:(MiscDrawState *)aState
/*"	Draws the fill of the receiver. There should be a view with locked focus
	when this method is called.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results.
	The method compares the setting it has to make to the postscript graphic state
	with the values in aState. aState should have valid values reflecting the
	current postscript graphic state.
	The changes to the postscript graphic state are written back in aState."*/
{
	NSColor * fillColor;
	
	if(resizeFlag){
		if(![aState->color isEqual:[NSColor lightGrayColor]]){
                    	if(aState->color)
                                  [aState->color release];
			aState->color = [[NSColor lightGrayColor] retain];
			[aState->color set];
		}
		[shape drawFill];
	} else {
		if([style fill]){
			fillColor = [style fillColor];
			if(![aState->color isEqual:fillColor]){
                            	if(aState->color)
                                       [aState->color release];
				aState->color = [fillColor retain];
				[fillColor set];
			}
			[shape drawFill];
		}
	}
}

- (void)drawEnding:(MiscDrawState *)aState
/*"	Draws the line endings of the receiver. There should be a view with locked focus
	when this method is called.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results.
	The method compares the setting it has to make to the postscript graphic state
	with the values in aState. aState should have valid values reflecting the
	current postscript graphic state.
	The changes to the postscript graphic state are written back in aState."*/
{
    	NSPoint outPoint,iPoint;
        float rotAngle;
        MiscDiagramTree *ptr;

        if([style childEnding] != MiscNoEnding && [self parent]){
            if([[diagram treeStyle] lineType]== MiscBendLineType)
                    outPoint = [self childEndPoint];
            else
                    outPoint = [(MiscDiagramTree *)[self parent] endPoint];
            if(![aState->color isEqual:[NSColor blackColor]]){
                    if(aState->color)
                            [aState->color release];
                    aState->color = [[NSColor blackColor] retain];
                    [aState->color set];
            }
            if(aState->linewidth != 0.15){
                    aState->linewidth = 0.15;
                    PSsetlinewidth(0.15);
            }
            [shape calcIntersection:&iPoint angle:&rotAngle toPoint:outPoint];
            switch([style childEnding]){
                    case MiscArrowEnding:
                            MiscPSWEndingSolid([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                            [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                    break;
                    case MiscHollowEnding:
                            MiscPSWEndingHollow([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                            [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                    break;
                    case MiscDoubleEnding:
                            MiscPSWEndingSolid([doubleEnding bboxParams], [doubleEnding lengthParams] + 4,
                                           [doubleEnding bboxOps], [doubleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);	                    			    break;
                    case MiscSolidEnding:
                            MiscPSWEndingSolid([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                           [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                    break;
                    case MiscCircleEnding:
                            MiscPSWEndingHollow([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                            [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                    break;
                    default:
                    break;
            }
        }
        if([style parentEnding] != MiscNoEnding && [self child] && ![self isCollapsed]){
            if(![aState->color isEqual:[NSColor blackColor]]){
                       if(aState->color)
                             [aState->color release];
                        aState->color = [[NSColor blackColor] retain];
                        [aState->color set];
            }
            if(aState->linewidth != 0.15){
                        aState->linewidth = 0.15;
                        PSsetlinewidth(0.15);
            }
            if([[diagram treeStyle] lineType]== MiscBendLineType){
                    outPoint = [self parentEndPoint];
                    [shape calcIntersection:&iPoint angle:&rotAngle toPoint:outPoint];
                    switch([style parentEnding]){
                      			case MiscArrowEnding:
                                                 MiscPSWEndingSolid([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                                                 [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                         break;
                                         case MiscHollowEnding:
                                                 MiscPSWEndingHollow([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                                                 [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                         break;
                                         case MiscDoubleEnding:
                                                 MiscPSWEndingSolid([doubleEnding bboxParams], [doubleEnding lengthParams] + 4,
                                                                [doubleEnding bboxOps], [doubleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);	                    			    		break;
                                         case MiscSolidEnding:
                                                 MiscPSWEndingSolid([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                                                [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                         break;
                                         case MiscCircleEnding:
                                                 MiscPSWEndingHollow([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                                                 [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                         break;
                                         default:
                                         break;
                    }
            } else {
                    for(ptr = (MiscDiagramTree *)[self child];ptr;ptr = (MiscDiagramTree *)[ptr sibling]){
                            outPoint = [ptr endPoint];
                            [shape calcIntersection:&iPoint angle:&rotAngle toPoint:outPoint];
                            switch([style parentEnding]){
                                	    case MiscArrowEnding:
                                                    MiscPSWEndingSolid([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                                                    [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                            break;
                                            case MiscHollowEnding:
                                                    MiscPSWEndingHollow([arrowEnding bboxParams], [arrowEnding lengthParams] + 4,
                                                                    [arrowEnding bboxOps], [arrowEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                            break;
                                            case MiscDoubleEnding:
                                                    MiscPSWEndingSolid([doubleEnding bboxParams], [doubleEnding lengthParams] + 4,
                                                        [doubleEnding bboxOps], [doubleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);	                    			    		    break;
                                            case MiscSolidEnding:
                                                    MiscPSWEndingSolid([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                                        [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                            break;
                                            case MiscCircleEnding:
                                                    MiscPSWEndingHollow([circleEnding bboxParams], [circleEnding lengthParams] + 4,
                                                                    [circleEnding bboxOps], [circleEnding lengthOps] + 2,iPoint.x,iPoint.y,rotAngle);
                                            break;
                                            default:
                                            break;
                                }	
                    }
            }
        }
	return;
}


- (void)drawCellsInView:(NSView *)aView attachments:(BOOL)aBool
/*"	Draws the label of the receiver. There should be a view with locked focus
	when this method is called.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results.
	The method draws the attachments knobs if the receiver has attachments and aBool
	has the value YES.
   	The method also draws the collapsed sign if the receiveris collapsed and aBool
        has the value YES."*/
{	
    	NSRect cellFrame;
    	BOOL wasAutodisplay;
        NSWindow *window;
        NSTextStorage *textStorage;
        NSRect ir;
        NSTextContainer *textContainer;
        
	if(resizeFlag)
		return;
        
        if(label){
                textStorage = [drawText textStorage];
                [textStorage setAttributedString:label];
                ir = [shape innerBounds];
                textContainer = [drawText textContainer];
                [textContainer setContainerSize:NSMakeSize(NSWidth(ir), miscTD_LargeNumberForText)];
                [drawText setMinSize:NSMakeSize(NSWidth(ir),(2.0 * [textContainer lineFragmentPadding]))];
        	[drawText setMaxSize:ir.size];

		if([style textPlacement] == MiscMiddleTextPlacement)
                	[drawText setAutoresizingMask:(NSViewMinYMargin | NSViewMaxYMargin)];
        	else if([style textPlacement] == MiscTopTextPlacement)
                	[drawText setAutoresizingMask:NSViewMinYMargin];
        	else
                	[drawText setAutoresizingMask:NSViewMaxYMargin];

		[drawText setFrame:ir];
		[drawText sizeToFit];

		cellFrame = [drawText frame];

                if([style textPlacement] == MiscMiddleTextPlacement){	       
			cellFrame.origin.y += (ir.size.height - cellFrame.size.height) / 2;
                } else if([style textPlacement] == MiscBottomTextPlacement){	
			cellFrame.origin.y += ir.size.height - cellFrame.size.height;
    		}

		[drawText setFrameOrigin:cellFrame.origin];

                window = [aView window];
                wasAutodisplay = [window isAutodisplay];
                if(wasAutodisplay)
                        [window setAutodisplay:NO]; // don't let addSubview: cause redisplay
                [aView addSubview:drawText];
                [drawText lockFocus];
                [drawText drawRect:[drawText bounds]];
                [drawText unlockFocus];
                [drawText removeFromSuperview];
                if(wasAutodisplay)
                        [window setAutodisplay:YES];
        }
	
        if(aBool && [self isCollapsed]){                
            	cellFrame = [self nodeBounds];
                cellFrame.origin.x += 8;
                cellFrame.origin.y -= 3;
                cellFrame.size.width = cellFrame.size.height = 14;
                if(!collapseImage)
                    	collapseImage = [NSImage imageNamed:@"miscTD_collapse"];
                [collapseImage compositeToPoint:cellFrame.origin operation:NSCompositeCopy];
        }
}

- (void)fillLines:(MiscUserPath *)linesPath andAddTo:(NSMutableArray *)nodes
/*"	Extends the linesPath with the line forming the connection to the
	receiver's parent and adds the receiver to the nodes list for drawing.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results."*/ 
{
	MiscDiagramTree *theChild,*theSibling, *theParent;
	
	theChild = (MiscDiagramTree *)[self child];
	theSibling = (MiscDiagramTree *)[self sibling];
	theParent = (MiscDiagramTree *)[self parent];	
	if(theParent && [theParent isCollapsed])
		return;
	[nodes addObject:self];
	if(theParent)
		[self _internalFillLinesIn:linesPath];
	if(theChild)
		[theChild fillLines:linesPath andAddTo:nodes];
	if(theSibling)
		[theSibling fillLines:linesPath andAddTo:nodes];
}

- (void)fillLines:(MiscUserPath *)linesPath andAddTo:(NSMutableArray *)nodes ifInvolvedInRect:(NSRect)aRect
/*"	Extends the linesPath with the line forming the connection to the
	receiver's parent if the line intersects aRect
	and adds the receiver to the nodes list for drawing
	if the receiver's bounds intersect aRect.
	The method does not check if the receiver needs layout.
	Calling the method with a node which is not already layed out
	has unpredictable results."*/
{
	MiscDiagramTree *theChild,*theSibling, *theParent;
	NSRect lineRect, theRect;
	float lx,ly,lw,lh;
        NSPoint mPos,pPos;
        NSSize mSize,pSize;

	theChild = (MiscDiagramTree *)[self child];
	theSibling = (MiscDiagramTree *)[self sibling];
	theParent = (MiscDiagramTree *)[self parent];	
	if(theParent && [theParent isCollapsed])
		return;
		
	theRect = [self nodeDrawBounds];
	if(NSIntersectsRect(aRect, theRect))
		[nodes addObject:self];
	if(theParent){
            	mPos = [self pos]; mSize = [self size];
                pPos = [theParent pos]; pSize = [theParent size];
		lx = MIN(mPos.x + mSize.width / 2, pPos.x + pSize.width / 2);
		ly = MIN(mPos.y + mSize.height / 2, pPos.y + pSize.height / 2);
		lw = MAX(ABS(mPos.x + mSize.width / 2 - pPos.x - pSize.width / 2),0.1);
		lh = MAX(ABS(mPos.y + mSize.height / 2 - pPos.y - pSize.height / 2),0.1);
		lineRect = NSMakeRect(lx,ly,lw,lh);
		if(NSIntersectsRect(aRect, lineRect))
			[self _internalFillLinesIn:linesPath];
	}
	if(theChild)
		[theChild fillLines:linesPath andAddTo:nodes ifInvolvedInRect:aRect];
	if(theSibling)
		[theSibling fillLines:linesPath andAddTo:nodes ifInvolvedInRect:aRect];
}

- (void)setResizingFlag
/*"	Changes the drawing mode of the receiver to interactive drawing for resizing.
	In this mode only a light gray shadow of the receiver is drawn with #drawFill:.
	The other drawing methods do nothing."*/  
{
	resizeFlag = 1;
}

- (void)resetResizingFlag
/*"	Sets the drawing mode back to normal."*/
{
	resizeFlag = 0;
}

- (void)drawResizingInView:(NSView *)aView viewScale:(float)aViewScale
/*"	Draws the receiver for interactive drawing mode.
	Used for resizing in aView.
	The value of viewScale gives the scaling factor of the
	current postscript graphic state. The method calculates the size of the
	knobs such that it appears constant onscreen regardless of the scaling factor."*/
{
	NSColor *fillColor;
	float linewidth;
	NSColor *outlineColor;
        BOOL wasAutodisplay;
        NSWindow *window;
        NSTextStorage *textStorage;
	NSRect cellFrame;
        NSRect ir;
	NSTextContainer *textContainer;

	if([style fill]){
		fillColor = [style fillColor];
		[fillColor set];
		[shape drawFill];
	}
	if([style outline]){
		linewidth = [style linewidth];
		outlineColor = [style outlineColor];
		[outlineColor set];
		PSsetlinewidth(linewidth);
		[shape drawOutline];
	}
	PSselectfont([controlFontname cString], MIN(controlFontSize / aViewScale, controlFontSize));
	[[NSColor blackColor] set];
	controlX = [self pos].x;
	controlY = [self pos].y;
	controlPts[0] = [self size].width;
	controlPts[1] = 0;
	controlPts[2] = 0; 
	controlPts[3] = [self size].height;
	controlPts[4] = -controlPts[0];
	controlPts[5] = 0;
	MiscPSWDrawControlPoints(controlX,controlY,controlPts,8,controlChars);
	[[NSColor darkGrayColor] set];
	MiscPSWDrawControlPoints(controlX,controlY,controlPts,8,controlPrimChars);
        
        if(label){
                textStorage = [drawText textStorage];
                [textStorage setAttributedString:label];
                ir = [shape innerBounds];
                textContainer = [drawText textContainer];
                [textContainer setContainerSize:NSMakeSize(NSWidth(ir), miscTD_LargeNumberForText)];
                [drawText setMinSize:NSMakeSize(NSWidth(ir),(2.0 * [textContainer lineFragmentPadding]))];
        	[drawText setMaxSize:ir.size];

		if([style textPlacement] == MiscMiddleTextPlacement)
                	[drawText setAutoresizingMask:(NSViewMinYMargin | NSViewMaxYMargin)];
        	else if([style textPlacement] == MiscTopTextPlacement)
                	[drawText setAutoresizingMask:NSViewMinYMargin];
        	else
                	[drawText setAutoresizingMask:NSViewMaxYMargin];

		[drawText setFrame:ir];
		[drawText sizeToFit];

		cellFrame = [drawText frame];

                if([style textPlacement] == MiscMiddleTextPlacement){	       
			cellFrame.origin.y += (ir.size.height - cellFrame.size.height) / 2;
                } else if([style textPlacement] == MiscBottomTextPlacement){	
			cellFrame.origin.y += ir.size.height - cellFrame.size.height;
    		}

		[drawText setFrameOrigin:cellFrame.origin];

                window = [aView window];
                wasAutodisplay = [window isAutodisplay];
                if(wasAutodisplay)
                        [window setAutodisplay:NO]; // don't let addSubview: cause redisplay
                [aView addSubview:drawText];
                [drawText lockFocus];
                [drawText drawRect:[drawText bounds]];
                [drawText unlockFocus];
                [drawText removeFromSuperview];
                if(wasAutodisplay)
                        [window setAutodisplay:YES];
        }
}

- (void)drawResizing
/*"	Draws fill and outline of the receiver for interactive drawing mode."*/
{
	NSColor *fillColor;
	float linewidth;
	NSColor *outlineColor;
	
	if([style fill]){
		fillColor = [style fillColor];
		[fillColor set];
		[shape drawFill];
	}
	if([style outline]){
		linewidth = [style linewidth];
		outlineColor = [style outlineColor];
		[outlineColor set];
		PSsetlinewidth(linewidth);
		[shape drawOutline];
	}
}

- (MiscShapeType)shapeType
/*"	Returns the shape type of the receiver."*/
{
	return [shape shapeType];
}

- (void)setShapeType:(MiscShapeType)aShapeType
/*"	Sets the shape of the receiver to aShapeType. If the receiver has the 
	%{automatic resizing flag} set then the size of the receiver
	changes such that the shape inner bounds stay the same."*/
{
	NSRect b,ib;
	
	ib = [shape innerBounds];
	b = [shape bounds];
	if([style automaticResize])
		b.size = [MiscDiagramShape calcSizeForInnerSize:ib.size shapeType:aShapeType];
	[shape release];
	shape = [[MiscDiagramShape allocWithZone:[self zone]] initWithShapeType:aShapeType bounds:b];
	if([style automaticResize])
		[self setSize:[shape bounds].size];
}

- (MiscDiagramShape *)shape
/*" Returns the shape of the receiver."*/
{
	return shape;    
}

- (NSAttributedString *)label
/*"	Returns the receiver's label."*/
{
	return label;
}

- (void)setLabel:(NSAttributedString *)aLabel
/*"	Sets the label of the receiver to aLabel. If the receiver has the 
	%{automatic resizing flag} set then the size changes to accomodate aLabel."*/
{
	//NSSize s;
	
    	if((!aLabel && !label) || (aLabel && [aLabel isEqual:label]))
            	return;
    	[label release];
        if(aLabel)
		label = [aLabel copyWithZone:[self zone]];
        else
            	label = nil;
	if([style automaticResize]){
		
	}
}

- (BOOL)isSelected
/*"	Returns whether the receiver is selected."*/
{
	return selected;
}

- (void)setSelected:(BOOL)aBOOL
/*"	Sets the receiver's selection status to aBOOL."*/
{
	selected = aBOOL;
}

- (BOOL)hit:(MiscHitPath *)hitPath controlPoint:(int *)aInt outline:(BOOL *)aBOOL
/*"	Returns YES if hitPath intersects one of the
	selection knobs. In this case aInt holds the number of the selection knob.
	There are four selection knobs positioned in the four corners of the 
	bounding rectangle and numbered counterclockwise starting with 0.
	If hitPath does not intersect a knob then returns YES if the receiver's shape 
	intersects hitPath and NO if not. In this case aInt is set to -1.
	If hitPath intersect the outline aBOOL is set to YES."*/
{
	NSRect hitBounds;
	NSPoint corner;
	BOOL didHit = NO;
	
	hitBounds = [hitPath bounds];
	corner = [self pos];
	*aInt = -1;
	*aBOOL = NO;
	if(NSPointInRect(*(const NSPoint *)&corner , *(const NSRect *)&hitBounds)){
		*aInt = 0;
		return YES;
	}	
	corner.x = [self pos].x + [self size].width;
	corner.y = [self pos].y;
	if(NSPointInRect(*(const NSPoint *)&corner , *(const NSRect *)&hitBounds)){
		*aInt = 1;
		return YES;
	}
	corner.x = [self pos].x + [self size].width;
	corner.y = [self pos].y + [self size].height;
	if(NSPointInRect(*(const NSPoint *)&corner , *(const NSRect *)&hitBounds)){
		*aInt = 2;
		return YES;
	}
	corner.x = [self pos].x;
	corner.y = [self pos].y + [self size].height;
	if(NSPointInRect(*(const NSPoint *)&corner , *(const NSRect *)&hitBounds)){
		*aInt = 3;
		return YES;
	}
	if([shape hitOutline:hitPath]){
		didHit = YES;
		*aBOOL = YES;
	} else if([shape hitFill:hitPath]){
		didHit = YES;
		*aBOOL = NO;
	}
	return didHit;
}	

- (NSPoint)controlPoint:(int)aInt
/*"	Returns the coordinates of the selection knob or control point aInt.
	There are four selection knobs positioned in the four corners of the 
	bounding rectangle and numbered counterclockwise starting with 0."*/
{
	NSPoint rp;

	if(aInt == 0){
		rp = [self pos];
	} else if(aInt == 1){
		rp.x = [self pos].x + [self size].width;
		rp.y = [self pos].y;
	} else if(aInt == 2){
		rp.x = [self pos].x + [self size].width;
		rp.y = [self pos].y + [self size].height;
	} else if(aInt == 3){
		rp.x = [self pos].x;
		rp.y = [self pos].y + [self size].height;
	} else {
		rp.x = 0;
		rp.y = 0;
	}	
	return rp;
}

- (NSRect)controlRect:(int)aInt
/*"	Returns the rectangle enclosing the selection knob or control point aInt.
	There are four selection knobs positioned in the four corners of the 
	bounding rectangle and numbered counterclockwise starting with 0."*/
{
	NSRect r;

	r.size.width = 4.0;
	r.size.height = 4.0;
	if(aInt == 0){
		r.origin = [self pos];
	} else if(aInt == 1){
		r.origin.x = [self pos].x + [self size].width;
		r.origin.y = [self pos].y;
	} else if(aInt == 2){
		r.origin.x = [self pos].x + [self size].width;
		r.origin.y = [self pos].y + [self size].height;
	} else if(aInt == 3){
		r.origin.x = [self pos].x;
		r.origin.y = [self pos].y + [self size].height;
	} else {
		r.origin.x = 0;
		r.origin.y = 0;
	}	
	r = NSOffsetRect(r , -2 , -2);
	return r;
}

- (void)resizeToControlPoint:(NSPoint)aPos :(int *)aInt
/*"	Temporarily resizes the receiver to reflect the new
	position aPos of selection knob aInt. It also changes
	aInt to a new selection knob number if aPos "tumbles"
	the receiver."*/ 
{
	NSRect newBounds;
	NSPoint lpt, rpt, cpt;
	int quadrant;
	
	lpt = newBounds.origin = [self pos];
	newBounds.size = [self size];
	rpt.x = newBounds.origin.x + newBounds.size.width;
	rpt.y = newBounds.origin.y + newBounds.size.height;
	
	if(*aInt == 0)
		cpt = rpt; 
	else if(*aInt == 1){
		cpt.x = lpt.x;
		cpt.y = rpt.y;
	} else if(*aInt == 2)
		cpt = lpt;
	else if(*aInt == 3){
		cpt.x = rpt.x;
		cpt.y = lpt.y;
	}	
	
	if(aPos.x > cpt.x){
		if(aPos.y > cpt.y)
			quadrant = 1;
		else
			quadrant = 4;
	} else {
		if(aPos.y > cpt.y)
			quadrant = 2;
		else
			quadrant = 3;
	}
	
	if(*aInt == 0){
		if(quadrant == 1){
			*aInt = 2;
			rpt = aPos;
			lpt.x = newBounds.origin.x + newBounds.size.width;
			lpt.y = newBounds.origin.y + newBounds.size.height;
                        [shape reflectVertical];
                        [shape reflectHorizontal];
		} else if(quadrant == 2){
			*aInt = 3;
			lpt.x = aPos.x;
			lpt.y = newBounds.origin.y + newBounds.size.height;
			rpt.y = aPos.y;
                        [shape reflectVertical];
		} else if(quadrant == 3){
			lpt = aPos;
		} else if(quadrant == 4){
			*aInt = 1;
			rpt.x = aPos.x;
			lpt.x = newBounds.origin.x + newBounds.size.width;
			lpt.y = aPos.y;
                        [shape reflectHorizontal];
		}
	} else if(*aInt == 1){
		if(quadrant == 1){
			*aInt = 2;
			rpt = aPos;
			lpt.y = newBounds.origin.y + newBounds.size.height;
                        [shape reflectVertical];
		} else if(quadrant == 2){
			*aInt = 3;
			lpt.y = newBounds.origin.y + newBounds.size.height;
			lpt.x = aPos.x;
			rpt.x = newBounds.origin.x + newBounds.size.width;
			rpt.y = aPos.y;
                        [shape reflectVertical];
                        [shape reflectHorizontal];
		} else if(quadrant == 3){
			*aInt = 0;
			lpt = aPos;
			rpt.x = newBounds.origin.x;
                        [shape reflectHorizontal];
		} else if(quadrant == 4){
			lpt.y = aPos.y;
			rpt.x = aPos.x;
		}
	} else if(*aInt == 2){
		if(quadrant == 1){
			rpt = aPos;
		} else if(quadrant == 2){
			*aInt = 3;
			rpt.x = newBounds.origin.x;
			lpt.x = aPos.x;
                        [shape reflectHorizontal];
		} else if(quadrant == 3){
			*aInt = 0;
			rpt.x = newBounds.origin.x;
			rpt.y = newBounds.origin.y;
			lpt = aPos;
                        [shape reflectVertical];
                        [shape reflectHorizontal];
		} else if(quadrant == 4){
			*aInt = 1;
			rpt.y = newBounds.origin.y;
			lpt.y = aPos.y;
                        [shape reflectVertical];
		}
	} else if(*aInt == 3){
		if(quadrant == 1){
			*aInt = 2;
			rpt = aPos;
			lpt.x = newBounds.origin.x + newBounds.size.width;
                        [shape reflectHorizontal];
		} else if(quadrant == 2){
			lpt.x = aPos.x;
			rpt.y = aPos.y;
		} else if(quadrant == 3){
			*aInt = 0;
			lpt = aPos;
			rpt.y = newBounds.origin.y;
                        [shape reflectVertical];
		} else if(quadrant == 4){
			*aInt = 1;
			lpt.x = newBounds.origin.x + newBounds.size.width;
			lpt.y = aPos.y;
			rpt.x = aPos.x;
			rpt.y = newBounds.origin.y;
                        [shape reflectVertical];
                        [shape reflectHorizontal];
		}
	}
	
	newBounds.origin = lpt;
	newBounds.size.width = rpt.x - lpt.x;
	newBounds.size.height = rpt.y - lpt.y;
	[self setTempNodeBounds:newBounds];
}

- (void)setDiagram:(MiscTreeDiagram *)aDiagram
/*"	Recursively sets the owning diagram of every node 
	of the tree rooted in the receiver."*/
{
	MiscDiagramTree *theChild,*theSibling;
	
	theChild = (MiscDiagramTree *)[self child];
	theSibling = (MiscDiagramTree *)[self sibling];
	diagram = aDiagram;
	if(theChild)
		[theChild setDiagram:aDiagram];
	if(theSibling)
		[theSibling setDiagram:aDiagram];
}
		
- (MiscTreeDiagram *)diagram
/*"	Returns the diagram the receiver belongs to."*/
{
	return diagram;
}

- (void)setNodeStyle:(MiscNodeStyle *)aStyle
/*"	Sets the receiver's node style to aStyle."*/
{
    	if(!aStyle || [style isEqual:aStyle])
            	return;
	[style release];
	style = [aStyle copyWithZone:[self zone]];
}

- (MiscNodeStyle *)nodeStyle
/*"	Returns the node style of the receiver."*/ 
{
	return style;
}
			              
@end

@implementation MiscDiagramTree(PrivateMethods)

- (void)_internalFillLinesIn:(MiscUserPath *)linesPath
{
	MiscTreeStyle *treeStyle;
	NSPoint l1,l2;
	float bendDistance;
	MiscDiagramTree *theParent;
	
	theParent = (MiscDiagramTree *)[self parent];
	treeStyle = [diagram treeStyle];
	if([treeStyle lineType] == MiscStraightLineType){
		[linesPath moveto:([self pos].x + [self size].width / 2) :([self pos].y + [self size].height / 2)];
		[linesPath lineto:([theParent pos].x + [theParent size].width / 2) 
			:([theParent pos].y + [theParent size].height / 2)];
	} else {
		bendDistance = ([self distanceToParent] + [self border]) * (1 - [treeStyle bendingFactor]) / 2;
		if([self layoutType] == MiscHorizontalTreeType){
			l1.x = [self pos].x - bendDistance; 
			l1.y = [self pos].y + [self size].height / 2;
			l2.x = [theParent pos].x + [theParent size].width + bendDistance;
			l2.y = [theParent pos].y + [theParent size].height / 2;
		} else {
			l1.x = [self pos].x + [self size].width / 2;
			l1.y = [self pos].y - bendDistance;
			l2.x = [theParent pos].x + [theParent size].width / 2;
			l2.y = [theParent pos].y + [theParent size].height + bendDistance;
		}
		[linesPath moveto:([self pos].x + [self size].width / 2) :([self pos].y + [self size].height / 2)];
		[linesPath lineto:l1.x :l1.y];
		[linesPath lineto:l2.x :l2.y];
		[linesPath lineto:([theParent pos].x + [theParent size].width / 2)
		 	:([theParent pos].y + [theParent size].height / 2)];
	}
}


@end
