summaryrefslogtreecommitdiff
path: root/tikzit/src/common/Edge.m
diff options
context:
space:
mode:
authorrandomguy3 <randomguy3@7c02a99a-9b00-45e3-bf44-6f3dd7fddb64>2012-01-09 11:00:50 +0000
committerrandomguy3 <randomguy3@7c02a99a-9b00-45e3-bf44-6f3dd7fddb64>2012-01-09 11:00:50 +0000
commita8a8dfb90d6a51ae369c042c95162f45754c7c4b (patch)
tree0e7a5f82febebe7129ebfb015f05b114064c39fd /tikzit/src/common/Edge.m
parente1cf0babff63e670e0d550b4072c22649a117fa7 (diff)
Move tikzit into "trunk" directory
git-svn-id: https://tikzit.svn.sourceforge.net/svnroot/tikzit/trunk@365 7c02a99a-9b00-45e3-bf44-6f3dd7fddb64
Diffstat (limited to 'tikzit/src/common/Edge.m')
-rw-r--r--tikzit/src/common/Edge.m525
1 files changed, 525 insertions, 0 deletions
diff --git a/tikzit/src/common/Edge.m b/tikzit/src/common/Edge.m
new file mode 100644
index 0000000..c89be6d
--- /dev/null
+++ b/tikzit/src/common/Edge.m
@@ -0,0 +1,525 @@
+//
+// 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 "util.h"
+
+@implementation Edge
+
+- (id)init {
+ [super init];
+ data = [[GraphElementData alloc] init];
+ bend = 0;
+ inAngle = 135;
+ outAngle = 45;
+ bendMode = EdgeBendModeBasic;
+ weight = 0.4f;
+ dirty = YES;
+ source = nil;
+ target = nil;
+ edgeNode = nil;
+
+ return self;
+}
+
+- (id)initWithSource:(Node*)s andTarget:(Node*)t {
+ [self init];
+
+ [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
+ style = [[EdgeStyle defaultEdgeStyleWithName:style_name] retain];
+ return NO;
+}
+
+- (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;
+
+ bend = normaliseAngleDeg (bend);
+ inAngle = normaliseAngleDeg (inAngle);
+ outAngle = normaliseAngleDeg (outAngle);
+ 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);
+ }
+
+ // 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.x = bezierInterpolate(0.5f, src.x, cp1.x, cp2.x, targ.x);
+ mid.y = bezierInterpolate(0.5f, src.y, cp1.y, cp2.y, targ.y);
+
+ dx = bezierInterpolate(0.6f, src.x, cp1.x, cp2.x, targ.x) -
+ bezierInterpolate(0.4f, src.x, cp1.x, cp2.x, targ.x);
+ dy = bezierInterpolate(0.6f, src.y, cp1.y, cp2.y, targ.y) -
+ bezierInterpolate(0.4f, src.y, cp1.y, cp2.y, targ.y);
+
+ // normalise
+ float len = sqrt(dx*dx+dy*dy);
+ if (len != 0) {
+ dx = (dx/len) * 0.1f;
+ dy = (dy/len) * 0.1f;
+ }
+
+ midTan.x = mid.x + dx;
+ midTan.y = mid.y + dy;
+ }
+ 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);
+
+ outAngle = round((angle - bnd) * (180.0f/M_PI));
+ inAngle = 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;
+
+ bend = (bend1 + bend2) / 2;
+
+ dirty = YES;
+}
+
+- (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)cp1 {
+ [self updateControls];
+ return cp1;
+}
+
+- (NSPoint)cp2 {
+ [self updateControls];
+ return cp2;
+}
+
+- (int)inAngle {return inAngle;}
+- (void)setInAngle:(int)a {
+ inAngle = a;
+ dirty = YES;
+}
+
+- (int)outAngle {return outAngle;}
+- (void)setOutAngle:(int)a {
+ outAngle = a;
+ dirty = YES;
+}
+
+- (EdgeBendMode)bendMode {return bendMode;}
+- (void)setBendMode:(EdgeBendMode)mode {
+ bendMode = mode;
+ dirty = YES;
+}
+
+- (int)bend {return bend;}
+- (void)setBend:(int)b {
+ bend = 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) {
+ [style release];
+ style = [s retain];
+ }
+}
+
+- (Node*)source {return source;}
+- (void)setSource:(Node *)s {
+ if (source != s) {
+ [source release];
+ source = [s retain];
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ dirty = YES;
+ }
+}
+
+- (Node*)target {return target;}
+- (void)setTarget:(Node *)t {
+ if (target != t) {
+ [target release];
+ target = [t retain];
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ 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 {
+#if defined (__APPLE__)
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+#endif
+ if (edgeNode != n) {
+ hasEdgeNode = (n != nil);
+ [edgeNode release];
+ edgeNode = [n retain];
+ // don't set dirty bit, because control points don't need update
+ }
+#if defined (__APPLE__)
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+#endif
+}
+
+- (BOOL)hasEdgeNode { return hasEdgeNode; }
+- (void)setHasEdgeNode:(BOOL)b {
+#if defined (__APPLE__)
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+#endif
+ hasEdgeNode = b;
+ if (hasEdgeNode && edgeNode == nil) {
+ edgeNode = [[Node alloc] init];
+ }
+#if defined (__APPLE__)
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+#endif
+}
+
+- (GraphElementData*)data {return data;}
+- (void)setData:(GraphElementData*)dt {
+ if (data != dt) {
+ [data release];
+ data = [dt copy];
+ }
+}
+
+- (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]) ||
+ (source!=target && weight == 0.4f)
+ ))
+ {
+ [data setProperty:[NSString stringWithFormat:@"%.2f",weight*2.5f]
+ forKey:@"looseness"];
+ }
+}
+
+- (void)setAttributesFromData {
+ bendMode = EdgeBendModeBasic;
+
+ if ([data isAtomSet:@"bend left"]) {
+ bend = -30;
+ } else if ([data isAtomSet:@"bend right"]) {
+ bend = 30;
+ } else if ([data propertyForKey:@"bend left"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend left"];
+ bend = -[bnd intValue];
+ } else if ([data propertyForKey:@"bend right"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend right"];
+ bend = [bnd intValue];
+ } else {
+ bend = 0;
+
+ if ([data propertyForKey:@"in"] != nil && [data propertyForKey:@"out"] != nil) {
+ bendMode = EdgeBendModeInOut;
+ inAngle = [[data propertyForKey:@"in"] intValue];
+ outAngle = [[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];
+ [en release];
+
+ GraphElementData *d = [[e data] copy];
+ [self setData:d];
+ [d release];
+
+ [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];
+ return NSRectAround4Points(src, targ, cp1, cp2);
+}
+
+- (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;
+
+ n = source;
+ source = target;
+ target = n;
+
+ f = inAngle;
+ inAngle = outAngle;
+ outAngle = f;
+
+ bend = -1 * bend;
+
+ dirty = YES;
+}
+
+- (void)dealloc {
+ [self setSource:nil];
+ [self setTarget:nil];
+ [self setData:nil];
+ [super dealloc];
+}
+
+- (id)copy {
+ Edge *cp = [[Edge alloc] init];
+ [cp setSource:[self source]];
+ [cp setTarget:[self target]];
+ [cp setPropertiesFromEdge:self];
+ return cp;
+}
+
++ (Edge*)edge {
+ return [[[Edge alloc] init] autorelease];
+}
+
++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t {
+ return [[[Edge alloc] initWithSource:s andTarget:t] autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4