This is MiscDiagramTree.m in view mode; [Download] [Up]
/* 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.