/*	MiscTreeDiagram.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 <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

#import "MiscTreeDiagram.h"
#import "MiscDiagramTree.h"
#import "MiscDiagramShape.h"
#import "MiscNodeStyle.h"
#import "MiscDiagramTree.h"
#import "MiscTreeDiagramView.h"
#import "MiscTDVPrivate.h"
#import <MiscFoundation/MiscUndoManager.h>

								 
@implementation MiscTreeDiagram
/*"	The MiscTreeDiagram class is the central class in the MiscTreeDiagram framework. An instance of this class
	manages a tree diagram and works closely with a MiscTreeDiagramView instance to display the diagram.
   
	The responsibilities of this class include :
		- offering a high-level API for manipulating the diagram
		- keeping the associated view instance in sync with the diagram
		- managing the node selection
		- maintaining an undo-redo mechanism
		- maintaining a printinfo object compatible with the printinfo object used in Diagram (tm)
		- managing the tree style of the diagram
		- keeping an associated document instance informed about the state of the diagram
		  as defined in the MiscTDDocAPI protocol.
   
   	The MiscTreeDiagram class defines a way to change the diagram more than once before the changes are reflected in the
   	associated view and document. To start a bulk of editing on the diagram use #beginEditing, to end the editing use
   	#endEditing. These two messages can be nested.

   	The message #acceptsDraggingWithHitPath:onOutline: lets associated views support dragging on nodes of the diagram.

   	The message #setRoot: sets the root of the tree diagram.One can think of this method as repointing the controller to
   	another model in a Model-View-Controller context, with a MiscDiagramTree instance being the model (the root of the tree),
   	the MiscTreeDiagram instance the controller and the associated MiscTreeDiagramView instance the view.
"*/


- initWithTreeStyle:(MiscTreeStyle *)aStyle
/*"Initializes a newly created instance of MiscTreeDiagram with tree style aStyle.
This is the designated initializer of this class."*/
{
	self = [super init];
	tree = nil; 
	selectedNode = nil;
	style = [aStyle retain];
	view = nil;
	doc = nil;
	editMode = 0; 
	printInfo = [[NSPrintInfo allocWithZone:[self zone]] init];
        undoManager = [[MiscUndoManager allocWithZone:[self zone]] init];
	return self;
}

- init
{  
   	self = [self initWithTreeStyle:[MiscTreeStyle defaultStyle]]; 	
   	return self;
}
   
- (void)dealloc
{
	[printInfo release];
        [undoManager release];
	if(tree)
		[tree release];
   	return [super dealloc];
}

- (MiscDiagramTree *)root
/*"Returns the root node of the receiver"*/
{
	return tree;
}

- (void)setRoot:(MiscDiagramTree *)aTree
/*"Sets the receiver's root node to aTree. This method 
releases the previous root node and retains aTree."*/ 
{
	if(tree == aTree)
		return;
        [aTree setDiagram:self];
        if(!editMode)
        	[undoManager startUndo:@"Undo Add Node" :@"Redo Delete Node"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager deleteNode:aTree];
        if(!editMode)
        	[undoManager commitUndo];
	[tree release];
	tree = [aTree retain];
	selectedNode = nil;
	if(!editMode && doc){
		[doc selectionChangedTo:selectedNode];
		[doc setDirty:YES];
	}
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:[tree nodeDrawBounds]];
}

- (void)addChild:(MiscDiagramTree *)aChild to:(MiscDiagramTree *)aNode atIndex:(unsigned)anIndex
/*"Adds aChild to aNode at index anIndex. This is the primitive method on which all other
#addChild... methods are based. This method is undoable. An index of NSNotFound indicates that
aChild should be put last in the list of children."*/
{
        if(!aChild || !aNode)
                return;
        [aChild setDiagram:self];
        if(!editMode)
        	[undoManager startUndo:@"Undo Add Node" :@"Redo Delete Node"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager deleteNode:aChild];
        if(!editMode)
        	[undoManager commitUndo];
        if(anIndex == NSNotFound)
        	[aNode addBranch:aChild];
        else
            	[aNode insertBranch:aChild at:anIndex];
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
            [view _internalScrollRectAndRepaintVisible:[aChild nodeDrawBounds]];
}

- (void)addChildToSelected:(MiscDiagramTree *)aChild
/*"Adds aChild as the last child to the currently selected node. This is a convenience
method that uses #addChild:to:atIndex:."*/ 
{
	if(!selectedNode || !aChild)
		return;
	[self addChild:aChild to:selectedNode atIndex:NSNotFound];
}

- (void)addNewChildToSelected
/*"Creates a node and adds it as the last child to the currently selected node. This is a convenience
method that uses #addChild:to:atIndex:."*/
{	
	if(!selectedNode)
		return;
	[self addChild:[MiscDiagramTree tree] to:selectedNode atIndex:NSNotFound];	
}

- (void)addChild:(MiscDiagramTree *)aChild to:(MiscDiagramTree *)aNode
/*"Adds aChild as the last child to aNode. This is a convenience
method that uses #addChild:to:atIndex:."*/
{
        if(!aChild || !aNode)
                return;
        [self addChild:aChild to:aNode atIndex:NSNotFound];
}

- (void)deleteSelected
/*"Deletes the curently selected node. This is a convenience method that
uses #deleteNode:."*/
{	
    	MiscDiagramTree *toBeDel;
        
	if(!selectedNode)
		return;
        toBeDel = selectedNode;
        [self beginEditing];
        [self unselectSelected];
	[self deleteNode:toBeDel];
        [self endEditing];
}

- (void)deleteNode:(MiscDiagramTree *)aNode
/*"Removes aNode from the diagram. This method is undoable."*/
{
        MiscDiagramTree *aTree;

        if(!aNode)
                return;
        if(aNode == selectedNode){
            	[self deleteSelected];
                return;
        }
        if(!editMode)
        	[undoManager startUndo:@"Undo Delete Node" :@"Redo Add Node"];
        [undoManager setCurrentTarget:self];
        aTree = (MiscDiagramTree *)[aNode parent];
        if(aTree){
            	[(id)undoManager addChild:aNode to:aTree atIndex:[aTree indexOfChild:aNode]];
                [aTree removeBranch:aNode];
        } else {
            	[(id)undoManager setRoot:tree];
                [tree release];
                tree = nil;
        }
        if(!editMode)
        	[undoManager commitUndo];
        if(!editMode && doc)
                [doc setDirty:YES];	
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];	
}

- (void)rearrangeChild:(MiscDiagramTree *)aChild toIndex:(unsigned)anIndex
/*"Rearranges the list of children of aChild's parent such that aChild is at index anIndex.
This method is undoable."*/
{
        MiscDiagramTree *aTree;
        unsigned oldIndex;

        if(!aChild || ![aChild parent])
                return;
        aTree = (MiscDiagramTree *)[aChild parent];
        oldIndex = [aTree indexOfChild:aChild];
        if(anIndex == oldIndex)
            	return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Rearrange Node" :@"Redo Rearrange Node"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager rearrangeChild:aChild toIndex:oldIndex];
        if(!editMode)
        	[undoManager commitUndo];        
        [aTree moveBranchAt:oldIndex to:anIndex];
        if(!editMode && doc)
                [doc setDirty:YES];	
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];	
}

- (MiscDiagramTree *)selectedNode
/*"Returns the currently selected node in the diagram."*/
{
	return selectedNode;
}

- (void)selectNode:(MiscDiagramTree *)aNode
/*"Makes aNode the currently selected node in the diagram. This is the
primitive method which all other #select... use."*/
{
	NSRect rp1, rp2;
	
	if(selectedNode == aNode || !aNode)
		return;
	if(!selectedNode){
		[aNode setSelected:YES];
		rp1 = [aNode nodeDrawBounds];
		selectedNode = aNode;
		if(!editMode && doc)
			[doc selectionChangedTo:selectedNode];
		if(!editMode && view)
			[view _internalScrollRect:rp1 andRepaintRect:rp1];
	} else {
		rp1 = [selectedNode nodeDrawBounds];
		[selectedNode setSelected:NO];
		[aNode setSelected:YES];
		rp2 = [aNode nodeDrawBounds];
		selectedNode = aNode;
		if(!editMode && doc)
			[doc selectionChangedTo:selectedNode];
		if(!editMode && view)
			[view _internalScrollRect:rp2 andRepaintRects:rp1 :rp2];
	}
}

- (void)unselectSelected
/*"No node is selected in the diagram after calling this method."*/
{
	MiscDiagramTree *aTree;
	NSRect rp;
	
	if(!selectedNode)
		return;
	aTree = selectedNode;
	selectedNode = nil;
	rp = [aTree nodeDrawBounds];
	[aTree setSelected:NO];	
	if(!editMode && doc)
		[doc selectionChangedTo:selectedNode];
	if(!editMode && view)
		[view _internalScrollRect:NSZeroRect andRepaintRect:rp];
}

- (MiscDiagramTree *)selectNodeWithHitPath:(MiscHitPath *)aPath
  controlPoint:(int *)aInt
/*Selects a node in the diagram if it intersects with aPath. If a control point
of the node is hit, its number is written in aInt, otherwise -1 is written in aInt.
If no node intersects aPath then the currently selected node gets deselected."*/
{
	NSEnumerator *enumerator;
	MiscDiagramTree *node = nil;
	BOOL found, didHitOutline;
	
	found = NO;
	*aInt = - 1;
	enumerator = [tree depthEnumerator];
	while(!found && (node = [enumerator nextObject]))
		found = [node hit:aPath controlPoint:aInt outline:&didHitOutline];
	if(found)
		[self selectNode:node];
	else
		[self unselectSelected];		
	return node;
}

- (void)selectNextSibling
/*"Selects the next sibling of the currently selected node if both exist.
This is a convenience method that uses #selectNode:."*/
{
	if(selectedNode && [selectedNode sibling])
		[self selectNode:(MiscDiagramTree *)[selectedNode sibling]];
}
		
- (void)selectPreviousSibling
/*"Selects the previous sibling of the currently selected node if both exist.
This is a convenience method that uses #selectNode:."*/
{
	MiscDiagramTree *iTree;
	
	if(selectedNode && [selectedNode parent]){
		iTree = (MiscDiagramTree *)[selectedNode parent];
		iTree = (MiscDiagramTree *)[iTree child];
		if(iTree != selectedNode){
			while((MiscDiagramTree *)[iTree sibling] != selectedNode)
				iTree = (MiscDiagramTree *)[iTree sibling];
			[self selectNode:iTree];
		}
	}
}

- (void)selectChild
/*"Selects the first child of the currently selected node if both exist.
This is a convenience method that uses #selectNode:."*/
{
	if(selectedNode && [selectedNode child] && ![selectedNode isCollapsed])
		[self selectNode:(MiscDiagramTree *)[selectedNode child]];
}

- (void)selectParent
/*"Selects the parent of the currently selected node if both exist.
This is a convenience method that uses #selectNode:."*/
{
	if(selectedNode && [selectedNode parent])
		[self selectNode:(MiscDiagramTree *)[selectedNode parent]];
}

- (MiscDiagramTree *)acceptsDraggingWithHitPath:(MiscHitPath *)aPath
   onOutline:(BOOL *)onOutline
/*"Returns the node which intersects with hit path aPath. If the node intersects
aPath on its outline then YES gets written in anOutline. Returns nil if no node
intersects aPath."*/
{
	NSEnumerator *enumerator;
	MiscDiagramTree *node = nil;
	BOOL found;
	int dummy;
	
	found = NO;
	dummy = - 1;
	enumerator = [tree depthEnumerator];
	while(!found && (node = [enumerator nextObject]))
		found = [node hit:aPath controlPoint:&dummy outline:onOutline];		
	return node;
}

- (void)setNodeStyle:(MiscNodeStyle *)aStyle of:(MiscDiagramTree *)aNode
/*"Sets the node style of aNode to aStyle. This method is undoable."*/
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;

        if(!aNode || !aStyle)
                return;
        nodeStyle = [aNode nodeStyle];
        if([aStyle isEqual:nodeStyle])
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Node Style" :@"Redo Change Node Style"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setNodeStyle:nodeStyle of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        drawRect = [aNode nodeDrawBounds];
        [aNode setNodeStyle:aStyle];
        drawRect = NSUnionRect(drawRect,[aNode nodeDrawBounds]);
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setFillColor:(NSColor *)aColor of:(MiscDiagramTree *)aNode
/*"Sets the fill color of aNode to aColor. This method is undoable."*/
{
	NSRect drawRect;
	MiscNodeStyle *nodeStyle;
        NSColor *oldColor;
        
	if(!aNode)
		return;
        nodeStyle = [aNode nodeStyle];
        oldColor = [nodeStyle fillColor];
        if([oldColor isEqual:aColor])
            	return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Fill Color" :@"Redo Change Fill Color"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setFillColor:oldColor of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
	[nodeStyle setFillColor:aColor];
	drawRect = [aNode nodeDrawBounds];
	if(!editMode && doc)
		[doc setDirty:YES];
	if(!editMode && view)
		[view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setOutlineColor:(NSColor *)aColor of:(MiscDiagramTree *)aNode
/*"Sets the outline color of aNode to aColor. This method is undoable."*/
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        NSColor *oldColor;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldColor = [nodeStyle outlineColor];
        if([oldColor isEqual:aColor])
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Outline Color" :@"Redo Change Outline Color"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setOutlineColor:oldColor of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        [nodeStyle setOutlineColor:aColor];
        drawRect = [aNode nodeDrawBounds];
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setChildEnding:(MiscEndingType)anEnding of:(MiscDiagramTree *)aNode
/*"Sets the child ending of aNode to anEnding. This method is undoable."*/
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        MiscEndingType oldEnding;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldEnding = [nodeStyle childEnding];
        if(oldEnding == anEnding)
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Ending" :@"Redo Change Ending"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setChildEnding:oldEnding of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        drawRect = [aNode nodeDrawBounds];
        [nodeStyle setChildEnding:anEnding];
        drawRect = NSUnionRect(drawRect,[aNode nodeDrawBounds]);
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setParentEnding:(MiscEndingType)anEnding of:(MiscDiagramTree *)aNode
/*"Sets the parent ending of aNode to anEnding. This method is undoable."*/
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        MiscEndingType oldEnding;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldEnding = [nodeStyle parentEnding];
        if(oldEnding == anEnding)
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Ending" :@"Redo Change Ending"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setParentEnding:oldEnding of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        drawRect = [aNode nodeDrawBounds];
        [nodeStyle setParentEnding:anEnding];
        drawRect = NSUnionRect(drawRect,[aNode nodeDrawBounds]);
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setLinewidth:(float)aLinewidth of:(MiscDiagramTree *)aNode
/*"Sets the line width of aNode to aLinewidth. This method is undoable."*/
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        float oldLinewidth;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldLinewidth = [nodeStyle linewidth];
        if(oldLinewidth == aLinewidth)
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Linewidth" :@"Redo Change Linewidth"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setLinewidth:oldLinewidth of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        drawRect = [aNode nodeDrawBounds];
        [nodeStyle setLinewidth:aLinewidth];
        drawRect = NSUnionRect(drawRect,[aNode nodeDrawBounds]);
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setShapeType:(MiscShapeType)aShapeType of:(MiscDiagramTree *)aNode
/*"Sets the shape type of aNode to aShapeType. This method is undoable."*/
{
	NSRect drawRect;
    	MiscShapeType oldShapeType;
        NSSize is,os;
        	
	if(!aNode)
		return;
        oldShapeType = [aNode shapeType];
        if(oldShapeType == aShapeType)
            	return;
	drawRect = [aNode nodeDrawBounds];
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Shape" :@"Redo Change Shape"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setShapeType:oldShapeType of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        is = [[aNode shape] innerBounds].size;
	[aNode setShapeType:aShapeType];
        if([[aNode nodeStyle] automaticResize]){       
            os = [MiscDiagramShape calcSizeForInnerSize:is shapeType:aShapeType];
            [aNode setSize:os];
            if(!editMode && view)
                   [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        } else {
            if(!editMode && view)
                   [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
        }	
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)toggleCollapseNode:(MiscDiagramTree *)aNode
/*"Toggles the collapse state of aNode."*/
{
        [aNode toggleCollapse];
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        if(!editMode && doc)
                [doc setDirty:YES];
}

- (void)setSize:(NSSize)aSize of:(MiscDiagramTree *)aNode
/*"Sets the size of aNode to aSize. This method is undoable."*/
{
        NSSize oldSize;

        if(!aNode)
                return;
        oldSize = [aNode size];
        if(oldSize.width == aSize.width && oldSize.height == aSize.height)
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Size" :@"Redo Change Size"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setSize:oldSize of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        [aNode setSize:aSize];
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        if(!editMode && doc)
                [doc setDirty:YES];
}

- (void)setTextPlacement:(MiscTextPlacement)aTextPlacement of:(MiscDiagramTree *)aNode
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        MiscTextPlacement oldTextPlacement;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldTextPlacement = [nodeStyle textPlacement];
        if(oldTextPlacement == aTextPlacement)
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Text Placement" :@"Redo Change Text Placement"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setTextPlacement:oldTextPlacement of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        drawRect = [aNode nodeDrawBounds];
        [nodeStyle setTextPlacement:aTextPlacement];
        if(!editMode && doc)
                [doc setDirty:YES];
        if(!editMode && view)
                [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
}

- (void)setAutomaticResize:(BOOL)aBool of:(MiscDiagramTree *)aNode
{
    
}    

- (void)setLabel:(NSAttributedString *)aLabel of:(MiscDiagramTree *)aNode
{
        NSRect drawRect;
        MiscNodeStyle *nodeStyle;
        NSAttributedString *oldLabel;

        if(!aNode)
                return;
        nodeStyle = [aNode nodeStyle];
        oldLabel = [aNode label];
        if([oldLabel isEqual:aLabel])
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Edit Label" :@"Redo Edit Label"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setLabel:oldLabel of:aNode];
        if(!editMode)
        	[undoManager commitUndo];
        [aNode setLabel:aLabel];
        if(!editMode && view){
                if([nodeStyle automaticResize])
                    	[view _internalScrollRectAndRepaintVisible:NSZeroRect];
                else {
			drawRect = [aNode nodeDrawBounds];
                        [view _internalScrollRect:NSZeroRect andRepaintRect:drawRect];
		}
        }    
        if(!editMode && doc)
                [doc setDirty:YES];
}


- (MiscTreeStyle *)treeStyle
/*"Returns the tree style of the receiver."*/
{
	return style;
}

- (void)setDistanceToParent:(float)aDistance
/*"Sets the distanceToParent parameter of the receiver. This method is undoable."*/
{
        float oldDistance;

        if(!tree)
             return;
        oldDistance = [tree distanceToParent];
        if(oldDistance == aDistance)
             return;
        [undoManager startUndo:@"Undo Change Distance to Parent" :@"Redo Change Distance to Parent"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setDistanceToParent:oldDistance];
        if(!editMode)
        	[undoManager commitUndo];
	[tree setDistanceToParent:aDistance];
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:NSZeroRect];
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)setBorder:(float)aBorder
/*"Sets the border parameter of the receiver. This method is undoable."*/
{
        float oldBorder;

        if(!tree)
            return;
        oldBorder = [tree border];
        if(oldBorder == aBorder)
            return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Border" :@"Redo Change Border"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setBorder:oldBorder];
        if(!editMode)
        	[undoManager commitUndo];
        [tree setBorder:aBorder];
        if(!editMode && view)
            [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        if(!editMode && doc)
            [doc setDirty:YES];
}

- (void)setLineType:(MiscLineType)aType
/*"Sets the line type of the receiver. This method is undoable."*/
{
        MiscLineType oldLineType;

        if(!tree)
            return;
        oldLineType = [style lineType];
        if(oldLineType == aType)
            return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Line Type" :@"Redo Change Line Type"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setLineType:oldLineType];
        if(!editMode)
        	[undoManager commitUndo];
	[style setLineType:aType];
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:NSZeroRect];
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)setBendingFactor:(float)aFactor
/*"Sets the bending factor of the receiver. This method is undoable."*/
{
        float oldFactor;

        if(!tree)
            return;
        oldFactor = [style bendingFactor];
        if(oldFactor == aFactor)
            return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Bending Factor" :@"Redo Change Bending Factor"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setBendingFactor:oldFactor];
        if(!editMode)
        	[undoManager commitUndo];
	[style setBendingFactor:aFactor];
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:NSZeroRect];
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)setLayoutType:(MiscLayoutTreeType)aType
/*"Sets the layout type of the receiver. This method is undoable."*/
{
    	MiscLayoutTreeType oldType;

        if(!tree)
            return;
        oldType = [tree layoutType];
        if(oldType == aType)
            return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Layout" :@"Redo Change Layout"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setLayoutType:oldType];
        if(!editMode)
        	[undoManager commitUndo];
	[tree setLayoutType:aType];
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:NSZeroRect];
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)setTreeStyle:(MiscTreeStyle *)aStyle
/*"Sets the tree style of the receiver. This method is undoable."*/
{	
        if(aStyle || [style isEqual:aStyle])
            return;
 	if(!editMode)   
        	[undoManager startUndo:@"Undo Change Tree Style" :@"Redo Tree Style"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setTreeStyle:style];
        if(!editMode)
        	[undoManager commitUndo];
	[style release];
	style = [aStyle copyWithZone:[self zone]];
	if(!editMode && view)
		[view _internalScrollRectAndRepaintVisible:NSZeroRect];
	if(!editMode && doc)
		[doc setDirty:YES];
}

- (void)setShadow:(BOOL)aBool
/*"Sets whether the receiver has shadow. This method is undoable."*/
{
        BOOL oldBool;

        oldBool = [style shadow];
        if(oldBool == aBool)
            return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Shadow" :@"Redo Change Shadow"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setLayoutType:oldBool];
        if(!editMode)
        	[undoManager commitUndo];      
	[style setShadow:aBool];
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        if(!editMode && doc)
                [doc setDirty:YES];
}

- (void)setShadowColor:(NSColor *)aColor
/*"Sets the shadow color of the receiver. This method is undoable."*/
{
        NSColor *oldColor;

        oldColor = [style shadowColor];
        if([oldColor isEqual:aColor])
                return;
        if(!editMode)
        	[undoManager startUndo:@"Undo Change Shadow Color" :@"Redo Change Shadow Color"];
        [undoManager setCurrentTarget:self];
        [(id)undoManager setShadowColor:oldColor];
        if(!editMode)
        	[undoManager commitUndo];    
        [style setShadowColor:aColor];
        if(!editMode && view)
                [view _internalScrollRectAndRepaintVisible:NSZeroRect];
        if(!editMode && doc)
                [doc setDirty:YES];
}

- (MiscUndoManager *)undoManager
/*"Returns the undo manager."*/
{
    	return undoManager;
}

- (MiscTreeDiagramView *)view
/*"Returns the associated view that draws the receiver."*/
{
	return view;
}

- (void)setView:(MiscTreeDiagramView *)aView
/*"Sets the associated view that draws the receiver. The receiver has
a working relationship with aView and thus does not retain it to
avoid retain-release cycles. You have to ensure that aView remains
valid during this relationship. See the MiscTreeDiagramView class for details on
this relationship."*/
{	
	view = aView;
}

- (void)beginEditing
/*"Starts an editing session for the receiver. During this session the changes
made to the receiver are not shown in the associated view and not communicated
to the associated document. End the session with #endEditing."*/    
{
    	if(editMode == 0){
            	[undoManager startUndo:@"Undo Changes" :@"Redo Changes"];
            	//[undoManager setCurrentTarget:self];
                //[(id)undoManager beginEditing];
        }    
	editMode++;
}

- (void)endEditing
/*"Ends an editing session for the receiver and resyncs the associated view and
document instances with the changes made to the receiver in the editing session.
The document instance is set to dirty even if no actual changes have been made."*/
{
	editMode--;
    	if(editMode == 0){
            	//[undoManager setCurrentTarget:self];
                //[(id)undoManager endEditing];
            	[undoManager commitUndo];
		if(!editMode && view)
			[view _internalScrollRectAndRepaintVisible:NSZeroRect];
		if(!editMode && doc)
			[doc setDirty:YES];
        }        
}

- (void)setDoc:(id <MiscTDDocAPI>)aDoc
/*"Sets the associated document instance to aDoc. The receiver has
a working relationship with aDoc and thus does not retain it to
avoid retain-release cycles. You have to ensure that aDoc remains
valid during this relationship."*/
{
	doc = aDoc;
}

- (id <MiscTDDocAPI>)doc
/*"Returns the associated document instance."*/
{
	return doc;
}

- (NSPrintInfo *)printInfo
/*"Returns the print info object."*/
{
	return printInfo;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
	[coder encodeObject:tree];
	[coder encodeObject:printInfo];
	[coder encodeObject:style];
	[coder encodeConditionalObject:view];
	[coder encodeConditionalObject:doc];
}

- initWithCoder:(NSCoder *)coder
{
	tree = [[coder decodeObject] retain];
	printInfo = [[coder decodeObject] retain];
	style = [[coder decodeObject] copyWithZone:[self zone]];
	view = [coder decodeObject];
	doc = [coder decodeObject];
        undoManager = [[MiscUndoManager allocWithZone:[self zone]] init];
	selectedNode = nil;
	editMode = 0;
	return self;
}
			              
@end
	
	
	
	
	
