summaryrefslogtreecommitdiff
path: root/tikzit-old/src/common/Edge.m
diff options
context:
space:
mode:
Diffstat (limited to 'tikzit-old/src/common/Edge.m')
-rw-r--r--tikzit-old/src/common/Edge.m757
1 files changed, 757 insertions, 0 deletions
diff --git a/tikzit-old/src/common/Edge.m b/tikzit-old/src/common/Edge.m
new file mode 100644
index 0000000..0c88e9d
--- /dev/null
+++ b/tikzit-old/src/common/Edge.m
@@ -0,0 +1,757 @@
+//
+// Edge.m
+// TikZiT
+//
+// Copyright 2010 Aleks Kissinger. All rights reserved.
+//
+//
+// This file is part of TikZiT.
+//
+// TikZiT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TikZiT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TikZiT. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#import "Edge.h"
+#import "Shape.h"
+#import "util.h"
+
+@implementation Edge
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ data = [[GraphElementData alloc] init];
+ bend = 0;
+ inAngle = 135;
+ outAngle = 45;
+ bendMode = EdgeBendModeBasic;
+ weight = 0.4f;
+ dirty = YES;
+ source = nil;
+ target = nil;
+ edgeNode = nil;
+ sourceAnchor = @"";
+ targetAnchor = @"";
+ }
+ return self;
+}
+
+- (id)initWithSource:(Node*)s andTarget:(Node*)t {
+ self = [self init];
+ if (self) {
+ [self setSource:s];
+ [self setTarget:t];
+ edgeNode = nil;
+
+ dirty = YES;
+ }
+ return self;
+}
+
+- (BOOL)attachStyleFromTable:(NSArray*)styles {
+ NSString *style_name = [data propertyForKey:@"style"];
+
+ [self setStyle:nil];
+ if (style_name == nil) return YES;
+
+ for (EdgeStyle *s in styles) {
+ if ([[s name] compare:style_name]==NSOrderedSame) {
+ [self setStyle:s];
+ return YES;
+ }
+ }
+
+ // if we didn't find a style, fill in a default one
+#if __has_feature(objc_arc)
+ style = [EdgeStyle defaultEdgeStyleWithName:style_name];
+#else
+ style = [[EdgeStyle defaultEdgeStyleWithName:style_name] retain];
+#endif
+ return NO;
+}
+
+- (NSPoint) _findContactPointOn:(Node*)node at:(float)angle {
+ NSPoint rayStart = [node point];
+ Shape *shape = [node shape];
+ if (shape == nil) {
+ return rayStart;
+ }
+
+ Transformer *shapeTrans = [node shapeTransformer];
+ // rounding errors are a pain
+ NSRect searchArea = NSInsetRect([node boundsUsingShapeTransform:shapeTrans],-0.01,-0.01);
+ if (!NSPointInRect(rayStart, searchArea)) {
+ return rayStart;
+ }
+
+ NSPoint rayEnd = findExitPointOfRay (rayStart, angle, searchArea);
+
+ for (NSArray *path in [shape paths]) {
+ for (Edge *curve in path) {
+ NSPoint intersect;
+ [curve updateControls];
+ if (lineSegmentIntersectsBezier (rayStart, rayEnd,
+ [shapeTrans toScreen:curve->tail],
+ [shapeTrans toScreen:curve->cp1],
+ [shapeTrans toScreen:curve->cp2],
+ [shapeTrans toScreen:curve->head],
+ &intersect)) {
+ // we just keep shortening the line
+ rayStart = intersect;
+ }
+ }
+ }
+
+ return rayStart;
+}
+
+- (NSPoint) _findTanFor:(NSPoint)pt usingSpanFrom:(float)t1 to:(float)t2 {
+ float dx = bezierInterpolate(t2, tail.x, cp1.x, cp2.x, head.x) -
+ bezierInterpolate(t1, tail.x, cp1.x, cp2.x, head.x);
+ float dy = bezierInterpolate(t2, tail.y, cp1.y, cp2.y, head.y) -
+ bezierInterpolate(t1, tail.y, cp1.y, cp2.y, head.y);
+
+ // normalise
+ float len = sqrt(dx*dx+dy*dy);
+ if (len != 0) {
+ dx = (dx/len) * 0.1f;
+ dy = (dy/len) * 0.1f;
+ }
+
+ return NSMakePoint (pt.x + dx, pt.y + dy);
+}
+
+- (void)recalculateProperties {
+ dirty = YES;
+}
+
+- (void)updateControls {
+ // check for external modification to the node locations
+ if (src.x != [source point].x || src.y != [source point].y ||
+ targ.x != [target point].x || targ.y != [target point].y)
+ {
+ dirty = YES;
+ }
+
+ if (dirty) {
+ src = [source point];
+ targ = [target point];
+
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+
+ float angleSrc = 0.0f;
+ float angleTarg = 0.0f;
+
+ if (bendMode == EdgeBendModeBasic) {
+ float angle = good_atan(dx, dy);
+ float bnd = (float)bend * (M_PI / 180.0f);
+ angleSrc = angle - bnd;
+ angleTarg = M_PI + angle + bnd;
+ } else if (bendMode == EdgeBendModeInOut) {
+ angleSrc = (float)outAngle * (M_PI / 180.0f);
+ angleTarg = (float)inAngle * (M_PI / 180.0f);
+ }
+
+ tail = [self _findContactPointOn:source at:angleSrc];
+ head = [self _findContactPointOn:target at:angleTarg];
+
+ // give a default distance for self-loops
+ float cdist = (dx==0.0f && dy==0.0f) ? weight : sqrt(dx*dx + dy*dy) * weight;
+
+ cp1 = NSMakePoint(src.x + (cdist * cos(angleSrc)),
+ src.y + (cdist * sin(angleSrc)));
+
+ cp2 = NSMakePoint(targ.x + (cdist * cos(angleTarg)),
+ targ.y + (cdist * sin(angleTarg)));
+
+ mid = bezierInterpolateFull (0.5f, tail, cp1, cp2, head);
+ midTan = [self _findTanFor:mid usingSpanFrom:0.4f to:0.6f];
+
+ tailTan = [self _findTanFor:tail usingSpanFrom:0.0f to:0.1f];
+ headTan = [self _findTanFor:head usingSpanFrom:1.0f to:0.9f];
+ }
+ dirty = NO;
+}
+
+- (void)convertBendToAngles {
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+ float angle = good_atan(dx, dy);
+ float bnd = (float)bend * (M_PI / 180.0f);
+
+ [self setOutAngle:round((angle - bnd) * (180.0f/M_PI))];
+ [self setInAngle:round((M_PI + angle + bnd) * (180.0f/M_PI))];
+ dirty = YES;
+}
+
+- (void)convertAnglesToBend {
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+ int angle = round((180.0f/M_PI) * good_atan(dx, dy));
+
+ // compute bend1 and bend2 to match inAngle and outAngle, resp.
+ int bend1, bend2;
+
+ bend1 = outAngle - angle;
+ bend2 = angle - inAngle;
+
+ [self setBend:(bend1 + bend2) / 2];
+}
+
+- (BOOL)isSelfLoop {
+ return (source == target);
+}
+
+- (BOOL)isStraight {
+ return (bendMode == EdgeBendModeBasic && bend == 0);
+}
+
+- (NSPoint)mid {
+ [self updateControls];
+ return mid;
+}
+
+- (NSPoint)midTan {
+ [self updateControls];
+ return midTan;
+}
+
+- (NSPoint)leftNormal {
+ [self updateControls];
+ return NSMakePoint(mid.x + (mid.y - midTan.y), mid.y - (mid.x - midTan.x));
+}
+
+- (NSPoint)rightNormal {
+ [self updateControls];
+ return NSMakePoint(mid.x - (mid.y - midTan.y), mid.y + (mid.x - midTan.x));
+}
+
+- (NSPoint)headTan {
+ [self updateControls];
+ return headTan;
+}
+
+- (NSPoint)leftHeadNormal {
+ [self updateControls];
+ return NSMakePoint(headTan.x + (head.y - headTan.y), headTan.y - (head.x - headTan.x));
+}
+
+- (NSPoint)rightHeadNormal {
+ [self updateControls];
+ return NSMakePoint(headTan.x - (head.y - headTan.y), headTan.y + (head.x - headTan.x));
+}
+
+- (NSPoint)tailTan {
+ [self updateControls];
+ return tailTan;
+}
+
+- (NSPoint)leftTailNormal {
+ [self updateControls];
+ return NSMakePoint(tailTan.x + (tail.y - tailTan.y), tailTan.y - (tail.x - tailTan.x));
+}
+
+- (NSPoint)rightTailNormal {
+ [self updateControls];
+ return NSMakePoint(tailTan.x - (tail.y - tailTan.y), tailTan.y + (tail.x - tailTan.x));
+}
+
+- (NSPoint) head {
+ [self updateControls];
+ return head;
+}
+
+- (NSPoint) tail {
+ [self updateControls];
+ return tail;
+}
+
+- (NSPoint)cp1 {
+ [self updateControls];
+ return cp1;
+}
+
+- (NSPoint)cp2 {
+ [self updateControls];
+ return cp2;
+}
+
+- (int)inAngle {return inAngle;}
+- (void)setInAngle:(int)a {
+ inAngle = normaliseAngleDeg (a);
+ dirty = YES;
+}
+
+- (int)outAngle {return outAngle;}
+- (void)setOutAngle:(int)a {
+ outAngle = normaliseAngleDeg (a);
+ dirty = YES;
+}
+
+- (EdgeBendMode)bendMode {return bendMode;}
+- (void)setBendMode:(EdgeBendMode)mode {
+ bendMode = mode;
+ dirty = YES;
+}
+
+- (int)bend {return bend;}
+- (void)setBend:(int)b {
+ bend = normaliseAngleDeg (b);
+ dirty = YES;
+}
+
+- (float)weight {return weight;}
+- (void)setWeight:(float)w {
+// if (source == target) weight = 1.0f;
+// else weight = w;
+ weight = w;
+ dirty = YES;
+}
+
+- (EdgeStyle*)style {return style;}
+- (void)setStyle:(EdgeStyle*)s {
+ if (style != s) {
+#if __has_feature(objc_arc)
+ style = s;
+#else
+ [style release];
+ style = [s retain];
+#endif
+ }
+}
+
+- (Node*)source {return source;}
+- (void)setSource:(Node *)s {
+ if (source != s) {
+ [source removeObserver:self
+ forKeyPath:@"style"];
+
+ if ([s style] == nil) {
+ [self setSourceAnchor:@"center"];
+ } else if ([sourceAnchor isEqual:@"center"]) {
+ [self setSourceAnchor:@""];
+ }
+
+#if __has_feature(objc_arc)
+ source = s;
+#else
+ [source release];
+ source = [s retain];
+#endif
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ [source addObserver:self
+ forKeyPath:@"style"
+ options:NSKeyValueObservingOptionNew
+ | NSKeyValueObservingOptionOld
+ context:NULL];
+
+ dirty = YES;
+ }
+}
+
+- (Node*)target {return target;}
+- (void)setTarget:(Node *)t {
+ if (target != t) {
+ [target removeObserver:self
+ forKeyPath:@"style"];
+
+ if ([t style] == nil) {
+ [self setTargetAnchor:@"center"];
+ } else if ([targetAnchor isEqual:@"center"]) {
+ [self setTargetAnchor:@""];
+ }
+
+#if __has_feature(objc_arc)
+ target = t;
+#else
+ [target release];
+ target = [t retain];
+#endif
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ [target addObserver:self
+ forKeyPath:@"style"
+ options:NSKeyValueObservingOptionNew
+ | NSKeyValueObservingOptionOld
+ context:NULL];
+
+ dirty = YES;
+ }
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+
+{
+ if ([keyPath isEqual:@"style"]) {
+ id oldStyle = [change objectForKey:NSKeyValueChangeOldKey];
+ id newStyle = [change objectForKey:NSKeyValueChangeNewKey];
+ id none = [NSNull null];
+ if (object == source) {
+ if (oldStyle != none && newStyle == none) {
+ [self setSourceAnchor:@"center"];
+ } else if (oldStyle == none && newStyle != none &&
+ [sourceAnchor isEqual:@"center"]) {
+ [self setSourceAnchor:@""];
+ }
+ }
+ if (object == target) {
+ if (oldStyle != none && newStyle == none) {
+ [self setTargetAnchor:@"center"];
+ } else if (oldStyle == none && newStyle != none &&
+ [targetAnchor isEqual:@"center"]) {
+ [self setTargetAnchor:@""];
+ }
+ }
+ }
+ dirty = YES;
+}
+
+
+// edgeNode and hasEdgeNode use a bit of key-value observing to help the mac GUI keep up
+
+- (Node*)edgeNode {return edgeNode;}
+- (void)setEdgeNode:(Node *)n {
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+ if (edgeNode != n) {
+ hasEdgeNode = (n != nil);
+#if __has_feature(objc_arc)
+ edgeNode = n;
+#else
+ [edgeNode release];
+ edgeNode = [n retain];
+#endif
+ // don't set dirty bit, because control points don't need update
+ }
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+}
+
+- (BOOL)hasEdgeNode { return hasEdgeNode; }
+- (void)setHasEdgeNode:(BOOL)b {
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+ hasEdgeNode = b;
+ if (hasEdgeNode && edgeNode == nil) {
+ edgeNode = [[Node alloc] init];
+ }
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+}
+
+- (NSString*) sourceAnchor { return sourceAnchor; }
+- (void)setSourceAnchor:(NSString *)_sourceAnchor{
+ NSString *oldSourceAnchor = sourceAnchor;
+ if(_sourceAnchor != nil){
+ sourceAnchor = [_sourceAnchor copy];
+ }else{
+ sourceAnchor = @"";
+ }
+#if ! __has_feature(objc_arc)
+ [oldSourceAnchor release];
+#endif
+}
+
+- (NSString*) targetAnchor { return targetAnchor; }
+- (void)setTargetAnchor:(NSString *)_targetAnchor{
+ NSString *oldTargetAnchor = targetAnchor;
+ if(_targetAnchor != nil){
+ targetAnchor = [_targetAnchor copy];
+ }else{
+ targetAnchor = @"";
+ }
+#if ! __has_feature(objc_arc)
+ [oldTargetAnchor release];
+#endif
+}
+
+@synthesize data;
+- (void) insertObject:(GraphElementProperty*)gep
+ inDataAtIndex:(NSUInteger)index {
+ [data insertObject:gep atIndex:index];
+}
+- (void) removeObjectFromDataAtIndex:(NSUInteger)index {
+ [data removeObjectAtIndex:index];
+}
+- (void) replaceObjectInDataAtIndex:(NSUInteger)index
+ withObject:(GraphElementProperty*)gep {
+ [data replaceObjectAtIndex:index withObject:gep];
+}
+
+- (void)updateData {
+ // unset everything to avoid redundant defs
+ [data unsetAtom:@"loop"];
+ [data unsetProperty:@"in"];
+ [data unsetProperty:@"out"];
+ [data unsetAtom:@"bend left"];
+ [data unsetAtom:@"bend right"];
+ [data unsetProperty:@"bend left"];
+ [data unsetProperty:@"bend right"];
+ [data unsetProperty:@"looseness"];
+
+ if (style == nil) {
+ [data unsetProperty:@"style"];
+ } else {
+ [data setProperty:[style name] forKey:@"style"];
+ }
+
+ if (bendMode == EdgeBendModeBasic && bend != 0) {
+ NSString *bendkey = @"bend right";
+ int b = [self bend];
+ if (b < 0) {
+ bendkey = @"bend left";
+ b = -b;
+ }
+
+ if (b == 30) {
+ [data setAtom:bendkey];
+ } else {
+ [data setProperty:[NSString stringWithFormat:@"%d",b] forKey:bendkey];
+ }
+
+ } else if (bendMode == EdgeBendModeInOut) {
+ [data setProperty:[NSString stringWithFormat:@"%d",inAngle]
+ forKey:@"in"];
+ [data setProperty:[NSString stringWithFormat:@"%d",outAngle]
+ forKey:@"out"];
+ }
+
+ // loop needs to come after in/out
+ if (source == target) [data setAtom:@"loop"];
+
+ if (![self isSelfLoop] && ![self isStraight])
+ {
+ [data setProperty:[NSString stringWithFormat:@"%.2f",weight*2.5f]
+ forKey:@"looseness"];
+ }
+}
+
+- (void)setAttributesFromData {
+ bendMode = EdgeBendModeBasic;
+
+ if ([data isAtomSet:@"bend left"]) {
+ [self setBend:-30];
+ } else if ([data isAtomSet:@"bend right"]) {
+ [self setBend:30];
+ } else if ([data propertyForKey:@"bend left"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend left"];
+ [self setBend:-[bnd intValue]];
+ } else if ([data propertyForKey:@"bend right"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend right"];
+ [self setBend:[bnd intValue]];
+ } else {
+ [self setBend:0];
+
+ if ([data propertyForKey:@"in"] != nil && [data propertyForKey:@"out"] != nil) {
+ bendMode = EdgeBendModeInOut;
+ [self setInAngle:[[data propertyForKey:@"in"] intValue]];
+ [self setOutAngle:[[data propertyForKey:@"out"] intValue]];
+ }
+ }
+
+ if ([data propertyForKey:@"looseness"] != nil) {
+ weight = [[data propertyForKey:@"looseness"] floatValue] / 2.5f;
+ } else {
+ weight = ([self isSelfLoop]) ? 1.0f : 0.4f;
+ }
+}
+
+- (void)setPropertiesFromEdge:(Edge*)e {
+ Node *en = [[e edgeNode] copy];
+ [self setEdgeNode:en];
+#if ! __has_feature(objc_arc)
+ [en release];
+#endif
+
+ GraphElementData *d = [[e data] copy];
+ [self setData:d];
+#if ! __has_feature(objc_arc)
+ [d release];
+#endif
+
+ [self setStyle:[e style]];
+ [self setBend:[e bend]];
+ [self setInAngle:[e inAngle]];
+ [self setOutAngle:[e outAngle]];
+ [self setBendMode:[e bendMode]];
+ [self setWeight:[e weight]];
+
+ dirty = YES; // cached data will be recomputed lazily, rather than copied
+}
+
+- (NSRect)boundingRect {
+ [self updateControls];
+ NSRect bound = NSRectAround4Points(head, tail, cp1, cp2);
+ if ([self style] != nil) {
+ switch ([[self style] decorationStyle]) {
+ case ED_Arrow:
+ bound = NSRectWithPoint(bound, [self midTan]);
+ case ED_Tick:
+ bound = NSRectWithPoint(bound, [self leftNormal]);
+ bound = NSRectWithPoint(bound, [self rightNormal]);
+ case ED_None:
+ break;
+ }
+ if ([[self style] headStyle] != AH_None) {
+ bound = NSRectWithPoint(bound, [self leftHeadNormal]);
+ bound = NSRectWithPoint(bound, [self rightHeadNormal]);
+ }
+ if ([[self style] tailStyle] != AH_None) {
+ bound = NSRectWithPoint(bound, [self leftTailNormal]);
+ bound = NSRectWithPoint(bound, [self rightTailNormal]);
+ }
+ }
+ return bound;
+}
+
+- (void) adjustWeight:(float)handle_dist withCourseness:(float)wcourseness {
+ float base_dist = NSDistanceBetweenPoints (src, targ);
+ if (base_dist == 0.0f) {
+ base_dist = 1.0f;
+ }
+
+ [self setWeight:roundToNearest(wcourseness, handle_dist / base_dist)];
+}
+
+- (float) angleOf:(NSPoint)point relativeTo:(NSPoint)base {
+ float dx = point.x - base.x;
+ float dy = point.y - base.y;
+ return radiansToDegrees (good_atan(dx, dy));
+}
+
+- (void) moveCp1To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link {
+ [self updateControls];
+ [self adjustWeight:NSDistanceBetweenPoints (point, src) withCourseness:wc];
+
+ float control_angle = [self angleOf:point relativeTo:src];
+ if (bendMode == EdgeBendModeBasic) {
+ float base_angle = [self angleOf:targ relativeTo:src];
+ int b = (int)roundToNearest (bc, base_angle - control_angle);
+ [self setBend:b];
+ } else {
+ int angle = (int)roundToNearest (bc, control_angle);
+ if (link) {
+ [self setInAngle:(inAngle + angle - outAngle)];
+ }
+ [self setOutAngle:angle];
+ }
+}
+
+- (void) moveCp1To:(NSPoint)point {
+ [self moveCp1To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO];
+}
+
+- (void) moveCp2To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link {
+ [self updateControls];
+
+ if (![self isSelfLoop]) {
+ [self adjustWeight:NSDistanceBetweenPoints (point, targ) withCourseness:wc];
+ }
+
+ float control_angle = [self angleOf:point relativeTo:targ];
+ if (bendMode == EdgeBendModeBasic) {
+ float base_angle = [self angleOf:src relativeTo:targ];
+ int b = (int)roundToNearest (bc, control_angle - base_angle);
+ [self setBend:b];
+ } else {
+ int angle = (int)roundToNearest (bc, control_angle);
+ if (link) {
+ [self setOutAngle:(outAngle + angle - inAngle)];
+ }
+ [self setInAngle: angle];
+ }
+}
+
+- (void) moveCp2To:(NSPoint)point {
+ [self moveCp2To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO];
+}
+
+- (void)reverse {
+ Node *n;
+ float f;
+ NSString *a;
+
+ n = source;
+ source = target;
+ target = n;
+
+ f = inAngle;
+ inAngle = outAngle;
+ outAngle = f;
+
+ a = sourceAnchor;
+ sourceAnchor = targetAnchor;
+ targetAnchor = a;
+
+ [self setBend:-bend];
+
+ dirty = YES;
+}
+
+- (void)dealloc {
+ [source removeObserver:self
+ forKeyPath:@"style"];
+ [target removeObserver:self
+ forKeyPath:@"style"];
+#if ! __has_feature(objc_arc)
+ [source release];
+ [target release];
+ [data release];
+ [sourceAnchor release];
+ [targetAnchor release];
+ [super dealloc];
+#endif
+}
+
+- (id)copyWithZone:(NSZone*)zone {
+ Edge *cp = [[Edge allocWithZone:zone] init];
+ [cp setSource:[self source]];
+ [cp setTarget:[self target]];
+ [cp setSourceAnchor:[self sourceAnchor]];
+ [cp setTargetAnchor:[self targetAnchor]];
+ [cp setPropertiesFromEdge:self];
+ return cp;
+}
+
++ (Edge*)edge {
+#if __has_feature(objc_arc)
+ return [[Edge alloc] init];
+#else
+ return [[[Edge alloc] init] autorelease];
+#endif
+}
+
++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t {
+#if __has_feature(objc_arc)
+ return [[Edge alloc] initWithSource:s andTarget:t];
+#else
+ return [[[Edge alloc] initWithSource:s andTarget:t] autorelease];
+#endif
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4