diff options
author | Aleks Kissinger <aleks0@gmail.com> | 2017-01-11 16:33:00 +0100 |
---|---|---|
committer | Aleks Kissinger <aleks0@gmail.com> | 2017-01-11 16:33:00 +0100 |
commit | 1802977b95d29198f27535b1b731d1180c083667 (patch) | |
tree | 032c4beb7411d88d76794a25f0e3b00a3437da3e /tikzit-1/src/common | |
parent | ff79a9c213dfd75ea00ed5112d3a6e314601e064 (diff) |
made new subdir
Diffstat (limited to 'tikzit-1/src/common')
73 files changed, 11443 insertions, 0 deletions
diff --git a/tikzit-1/src/common/CircleShape.h b/tikzit-1/src/common/CircleShape.h new file mode 100644 index 0000000..8215b92 --- /dev/null +++ b/tikzit-1/src/common/CircleShape.h @@ -0,0 +1,33 @@ +// +// CircleShape.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "Shape.h" + +@interface CircleShape : Shape { +} + + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/CircleShape.m b/tikzit-1/src/common/CircleShape.m new file mode 100644 index 0000000..f2d1d52 --- /dev/null +++ b/tikzit-1/src/common/CircleShape.m @@ -0,0 +1,57 @@ +// +// CircleShape.m +// TikZiT +// +// Copyright 2011 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 "CircleShape.h" +#import "Node.h" +#import "Edge.h" + +@implementation CircleShape + +- (id)init { + self = [super init]; + if (self) { + Node *n0,*n1,*n2,*n3; + + n0 = [Node nodeWithPoint:NSMakePoint( 0.0f, 0.2f)]; + n1 = [Node nodeWithPoint:NSMakePoint( 0.2f, 0.0f)]; + n2 = [Node nodeWithPoint:NSMakePoint( 0.0f, -0.2f)]; + n3 = [Node nodeWithPoint:NSMakePoint(-0.2f, 0.0f)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; [e0 setBend:-45]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; [e1 setBend:-45]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; [e2 setBend:-45]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; [e3 setBend:-45]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"circle"; + } + return self; +} + + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/ColorRGB.h b/tikzit-1/src/common/ColorRGB.h new file mode 100644 index 0000000..607ba64 --- /dev/null +++ b/tikzit-1/src/common/ColorRGB.h @@ -0,0 +1,64 @@ +// +// ColorRGB.h +// 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 <Foundation/Foundation.h> +#import "RColor.h" + +@interface ColorRGB : NSObject<NSCopying> { + unsigned short red, green, blue; +} + +@property (assign) unsigned short red; +@property (assign) unsigned short green; +@property (assign) unsigned short blue; + +@property (assign) float redFloat; +@property (assign) float greenFloat; +@property (assign) float blueFloat; + +@property (readonly) NSString *name; + +- (RColor)rColor; +- (RColor)rColorWithAlpha:(CGFloat)alpha; + +- (NSString*)hexName; +- (BOOL)isEqual:(id)col; +- (float)distanceFromColor:(ColorRGB*)col; +- (int)hash; + +- (id)initWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b; +- (id)initWithFloatRed:(float)r green:(float)g blue:(float)b; +- (id)initWithRColor:(RColor)color; + +- (void)setToClosestHashed; + ++ (ColorRGB*)colorWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b; ++ (ColorRGB*)colorWithFloatRed:(float)r green:(float)g blue:(float)b; ++ (ColorRGB*)colorWithRColor:(RColor)color; + ++ (void)makeColorHash; ++ (void)releaseColorHash; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/ColorRGB.m b/tikzit-1/src/common/ColorRGB.m new file mode 100644 index 0000000..840d716 --- /dev/null +++ b/tikzit-1/src/common/ColorRGB.m @@ -0,0 +1,353 @@ +// +// ColorRGB.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 "ColorRGB.h" +#import "util.h" + +typedef struct { +#if __has_feature(objc_arc) + __unsafe_unretained NSString *name; +#else + NSString *name; +#endif + unsigned short r, g, b; +} ColorRGBEntry; + +static const ColorRGBEntry kColors[147] = { + { @"AliceBlue", 240, 248, 255 }, + { @"AntiqueWhite", 250, 235, 215 }, + { @"Aqua", 0, 255, 255 }, + { @"Aquamarine", 127, 255, 212 }, + { @"Azure", 240, 255, 255 }, + { @"Beige", 245, 245, 220 }, + { @"Bisque", 255, 228, 196 }, + { @"Black", 0, 0, 0 }, + { @"BlanchedAlmond", 255, 235, 205 }, + { @"Blue", 0, 0, 255 }, + { @"BlueViolet", 138, 43, 226 }, + { @"Brown", 165, 42, 42 }, + { @"BurlyWood", 222, 184, 135 }, + { @"CadetBlue", 95, 158, 160 }, + { @"Chartreuse", 127, 255, 0 }, + { @"Chocolate", 210, 105, 30 }, + { @"Coral", 255, 127, 80 }, + { @"CornflowerBlue", 100, 149, 237 }, + { @"Cornsilk", 255, 248, 220 }, + { @"Crimson", 220, 20, 60 }, + { @"Cyan", 0, 255, 255 }, + { @"DarkBlue", 0, 0, 139 }, + { @"DarkCyan", 0, 139, 139 }, + { @"DarkGoldenrod", 184, 134, 11 }, + { @"DarkGray", 169, 169, 169 }, + { @"DarkGreen", 0, 100, 0 }, + { @"DarkGrey", 169, 169, 169 }, + { @"DarkKhaki", 189, 183, 107 }, + { @"DarkMagenta", 139, 0, 139 }, + { @"DarkOliveGreen", 85, 107, 47 }, + { @"DarkOrange", 255, 140, 0 }, + { @"DarkOrchid", 153, 50, 204 }, + { @"DarkRed", 139, 0, 0 }, + { @"DarkSalmon", 233, 150, 122 }, + { @"DarkSeaGreen", 143, 188, 143 }, + { @"DarkSlateBlue", 72, 61, 139 }, + { @"DarkSlateGray", 47, 79, 79 }, + { @"DarkSlateGrey", 47, 79, 79 }, + { @"DarkTurquoise", 0, 206, 209 }, + { @"DarkViolet", 148, 0, 211 }, + { @"DeepPink", 255, 20, 147 }, + { @"DeepSkyBlue", 0, 191, 255 }, + { @"DimGray", 105, 105, 105 }, + { @"DimGrey", 105, 105, 105 }, + { @"DodgerBlue", 30, 144, 255 }, + { @"FireBrick", 178, 34, 34 }, + { @"FloralWhite", 255, 250, 240 }, + { @"ForestGreen", 34, 139, 34 }, + { @"Fuchsia", 255, 0, 255 }, + { @"Gainsboro", 220, 220, 220 }, + { @"GhostWhite", 248, 248, 255 }, + { @"Gold", 255, 215, 0 }, + { @"Goldenrod", 218, 165, 32 }, + { @"Gray", 128, 128, 128 }, + { @"Grey", 128, 128, 128 }, + { @"Green", 0, 128, 0 }, + { @"GreenYellow", 173, 255, 47 }, + { @"Honeydew", 240, 255, 240 }, + { @"HotPink", 255, 105, 180 }, + { @"IndianRed", 205, 92, 92 }, + { @"Indigo", 75, 0, 130 }, + { @"Ivory", 255, 255, 240 }, + { @"Khaki", 240, 230, 140 }, + { @"Lavender", 230, 230, 250 }, + { @"LavenderBlush", 255, 240, 245 }, + { @"LawnGreen", 124, 252, 0 }, + { @"LemonChiffon", 255, 250, 205 }, + { @"LightBlue", 173, 216, 230 }, + { @"LightCoral", 240, 128, 128 }, + { @"LightCyan", 224, 255, 255 }, + { @"LightGoldenrodYellow", 250, 250, 210 }, + { @"LightGray", 211, 211, 211 }, + { @"LightGreen", 144, 238, 144 }, + { @"LightGrey", 211, 211, 211 }, + { @"LightPink", 255, 182, 193 }, + { @"LightSalmon", 255, 160, 122 }, + { @"LightSeaGreen", 32, 178, 170 }, + { @"LightSkyBlue", 135, 206, 250 }, + { @"LightSlateGray", 119, 136, 153 }, + { @"LightSlateGrey", 119, 136, 153 }, + { @"LightSteelBlue", 176, 196, 222 }, + { @"LightYellow", 255, 255, 224 }, + { @"Lime", 0, 255, 0 }, + { @"LimeGreen", 50, 205, 50 }, + { @"Linen", 250, 240, 230 }, + { @"Magenta", 255, 0, 255 }, + { @"Maroon", 128, 0, 0 }, + { @"MediumAquamarine", 102, 205, 170 }, + { @"MediumBlue", 0, 0, 205 }, + { @"MediumOrchid", 186, 85, 211 }, + { @"MediumPurple", 147, 112, 219 }, + { @"MediumSeaGreen", 60, 179, 113 }, + { @"MediumSlateBlue", 123, 104, 238 }, + { @"MediumSpringGreen", 0, 250, 154 }, + { @"MediumTurquoise", 72, 209, 204 }, + { @"MediumVioletRed", 199, 21, 133 }, + { @"MidnightBlue", 25, 25, 112 }, + { @"MintCream", 245, 255, 250 }, + { @"MistyRose", 255, 228, 225 }, + { @"Moccasin", 255, 228, 181 }, + { @"NavajoWhite", 255, 222, 173 }, + { @"Navy", 0, 0, 128 }, + { @"OldLace", 253, 245, 230 }, + { @"Olive", 128, 128, 0 }, + { @"OliveDrab", 107, 142, 35 }, + { @"Orange", 255, 165, 0 }, + { @"OrangeRed", 255, 69, 0 }, + { @"Orchid", 218, 112, 214 }, + { @"PaleGoldenrod", 238, 232, 170 }, + { @"PaleGreen", 152, 251, 152 }, + { @"PaleTurquoise", 175, 238, 238 }, + { @"PaleVioletRed", 219, 112, 147 }, + { @"PapayaWhip", 255, 239, 213 }, + { @"PeachPuff", 255, 218, 185 }, + { @"Peru", 205, 133, 63 }, + { @"Pink", 255, 192, 203 }, + { @"Plum", 221, 160, 221 }, + { @"PowderBlue", 176, 224, 230 }, + { @"Purple", 128, 0, 128 }, + { @"Red", 255, 0, 0 }, + { @"RosyBrown", 188, 143, 143 }, + { @"RoyalBlue", 65, 105, 225 }, + { @"SaddleBrown", 139, 69, 19 }, + { @"Salmon", 250, 128, 114 }, + { @"SandyBrown", 244, 164, 96 }, + { @"SeaGreen", 46, 139, 87 }, + { @"Seashell", 255, 245, 238 }, + { @"Sienna", 160, 82, 45 }, + { @"Silver", 192, 192, 192 }, + { @"SkyBlue", 135, 206, 235 }, + { @"SlateBlue", 106, 90, 205 }, + { @"SlateGray", 112, 128, 144 }, + { @"SlateGrey", 112, 128, 144 }, + { @"Snow", 255, 250, 250 }, + { @"SpringGreen", 0, 255, 127 }, + { @"SteelBlue", 70, 130, 180 }, + { @"Tan", 210, 180, 140 }, + { @"Teal", 0, 128, 128 }, + { @"Thistle", 216, 191, 216 }, + { @"Tomato", 255, 99, 71 }, + { @"Turquoise", 64, 224, 208 }, + { @"Violet", 238, 130, 238 }, + { @"Wheat", 245, 222, 179 }, + { @"White", 255, 255, 255 }, + { @"WhiteSmoke", 245, 245, 245 }, + { @"Yellow", 255, 255, 0 }, + { @"YellowGreen", 154, 205, 50 } +}; + +static NSMapTable *colorHash = nil; + +@implementation ColorRGB + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObject:@"red"] triggerChangeNotificationsForDependentKey:@"redFloat"]; + [self setKeys:[NSArray arrayWithObject:@"green"] triggerChangeNotificationsForDependentKey:@"greenFloat"]; + [self setKeys:[NSArray arrayWithObject:@"blue"] triggerChangeNotificationsForDependentKey:@"blueFloat"]; + [self setKeys:[NSArray arrayWithObjects:@"red", @"green", @"blue", nil] + triggerChangeNotificationsForDependentKey:@"name"]; +} + +@synthesize red, green, blue; + +- (float)redFloat { return ((float)red)/255.0f; } +- (void)setRedFloat:(float)r { [self setRed:round(r*255.0f)]; } +- (float)greenFloat { return ((float)green)/255.0f; } +- (void)setGreenFloat:(float)g { [self setGreen:round(g*255.0f)]; } +- (float)blueFloat { return ((float)blue)/255.0f; } +- (void)setBlueFloat:(float)b { [self setBlue:round(b*255.0f)]; } + +- (int)hash { + return (red<<4) + (green<<2) + blue; +} + +- (id)initWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b { + self = [super init]; + if (self) { + red = r; + green = g; + blue = b; + } + return self; +} + +- (id)initWithFloatRed:(float)r green:(float)g blue:(float)b { + self = [super init]; + if (self) { + red = round(r*255.0f); + green = round(g*255.0f); + blue = round(b*255.0f); + } + return self; +} + +- (id) initWithRColor:(RColor)color { + return [self initWithFloatRed:color.red green:color.green blue:color.blue]; +} + +- (RColor) rColor { + return MakeSolidRColor ([self redFloat], [self greenFloat], [self blueFloat]); +} + +- (RColor) rColorWithAlpha:(CGFloat)alpha { + return MakeRColor ([self redFloat], [self greenFloat], [self blueFloat], alpha); +} + +- (NSString*)name { + if (colorHash == nil) + [ColorRGB makeColorHash]; + return [colorHash objectForKey:self]; +} + +- (NSString*)hexName { + return [NSString stringWithFormat:@"hexcolor0x%.2x%.2x%.2x", red, green, blue]; +} + +- (NSComparisonResult)compare:(ColorRGB*)col { + if (red > [col red]) return NSOrderedDescending; + else if (red < [col red]) return NSOrderedAscending; + else { + if (green > [col green]) return NSOrderedDescending; + else if (green < [col green]) return NSOrderedAscending; + else { + if (blue > [col blue]) return NSOrderedDescending; + else if (blue < [col blue]) return NSOrderedAscending; + else return NSOrderedSame; + } + } +} + +- (BOOL)isEqual:(id)col { + return [self compare:col] == NSOrderedSame; +} + +- (float)distanceFromColor:(ColorRGB *)col { + float dr = (float)(red - [col red]); + float dg = (float)(green - [col green]); + float db = (float)(blue - [col blue]); + return dr*dr + dg*dg + db*db; +} + +- (id)copyWithZone:(NSZone*)zone { + ColorRGB *col = [[ColorRGB allocWithZone:zone] initWithRed:red green:green blue:blue]; + return col; +} + ++ (ColorRGB*)colorWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b { + ColorRGB *col = [[ColorRGB alloc] initWithRed:r green:g blue:b]; +#if __has_feature(objc_arc) + return col; +#else + return [col autorelease]; +#endif +} + + ++ (ColorRGB*)colorWithFloatRed:(float)r green:(float)g blue:(float)b { + ColorRGB *col = [[ColorRGB alloc] initWithFloatRed:r green:g blue:b]; +#if __has_feature(objc_arc) + return col; +#else + return [col autorelease]; +#endif +} + ++ (ColorRGB*) colorWithRColor:(RColor)color { +#if __has_feature(objc_arc) + return [[self alloc] initWithRColor:color]; +#else + return [[[self alloc] initWithRColor:color] autorelease]; +#endif +} + ++ (void)makeColorHash { + NSMapTable *h = [[NSMapTable alloc] init]; + int i; + for (i = 0; i < 147; ++i) { + ColorRGB *col = [[ColorRGB alloc] initWithRed:kColors[i].r + green:kColors[i].g + blue:kColors[i].b]; + [h setObject:kColors[i].name forKey:col]; + //NSLog(@"adding color %@ (%d)", kColors[i].name, [col hash]); +#if ! __has_feature(objc_arc) + [col release]; +#endif + } + colorHash = h; +} + ++ (void)releaseColorHash { +#if ! __has_feature(objc_arc) + [colorHash release]; +#endif +} + +- (void)setToClosestHashed { + if (colorHash == nil) + [ColorRGB makeColorHash]; + float bestDist = -1; + ColorRGB *bestColor = nil; + NSEnumerator *enumerator = [colorHash keyEnumerator]; + ColorRGB *tryColor; + while ((tryColor = [enumerator nextObject]) != nil) { + float dist = [self distanceFromColor:tryColor]; + if (bestDist<0 || dist<bestDist) { + bestDist = dist; + bestColor = tryColor; + } + } + [self setRed:[bestColor red]]; + [self setGreen:[bestColor green]]; + [self setBlue:[bestColor blue]]; +} + +@end + +// vi:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/DiamondShape.h b/tikzit-1/src/common/DiamondShape.h new file mode 100644 index 0000000..8f63386 --- /dev/null +++ b/tikzit-1/src/common/DiamondShape.h @@ -0,0 +1,34 @@ +// +// DiamondShape.h +// TikZiT +// +// Copyright 2012 Alex Merry +// 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 <Foundation/Foundation.h> +#import "Shape.h" + +@interface DiamondShape : Shape { +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 + diff --git a/tikzit-1/src/common/DiamondShape.m b/tikzit-1/src/common/DiamondShape.m new file mode 100644 index 0000000..1a578b8 --- /dev/null +++ b/tikzit-1/src/common/DiamondShape.m @@ -0,0 +1,63 @@ +// +// DiamondShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger +// Copyright 2012 Alex Merry +// 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 "DiamondShape.h" + +#import "Node.h" +#import "Edge.h" + +@implementation DiamondShape + +- (id)init { + self = [super init]; + + if (!self) + return nil; + + Node *n0,*n1,*n2,*n3; + float sz = 0.25f; + + n0 = [Node nodeWithPoint:NSMakePoint(0, sz)]; + n1 = [Node nodeWithPoint:NSMakePoint(sz, 0)]; + n2 = [Node nodeWithPoint:NSMakePoint(0,-sz)]; + n3 = [Node nodeWithPoint:NSMakePoint(-sz,0)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"shape=diamond"; + + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Edge.h b/tikzit-1/src/common/Edge.h new file mode 100644 index 0000000..accf38c --- /dev/null +++ b/tikzit-1/src/common/Edge.h @@ -0,0 +1,401 @@ +// +// Edge.h +// 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/>. +// + + +// Edge : store the data associated with an edge. Also, lazily compute +// bezier curve control points based on bend and the coordinates of +// the endpoints. + +#import "Node.h" +#import "EdgeStyle.h" + +/*! + @typedef enum EdgeBendMode + @brief Indicates the type of edge bend. + @var EdgeBendModeBasic A basic, one-angle bend. Positive values will be interpreted + as bend left, negative as bend right. + @var EdgeBendModeInOut A two-angle bend mode, using inAngle and outAngle. + */ +typedef enum { + EdgeBendModeBasic, + EdgeBendModeInOut +} EdgeBendMode; + +/*! + @class Edge + @brief A graph edge, with associated bend and style data. + @details A graph edge, with associated bend and style data. This class + also contains methods for computing the bezier control points + and the midpoint of the curve. + */ +@interface Edge : NSObject<NSCopying> { + Node *source; + Node *target; + Node *edgeNode; + int bend; + int inAngle, outAngle; + EdgeBendMode bendMode; + float weight; + EdgeStyle *style; + GraphElementData *data; + NSString *sourceAnchor; + NSString *targetAnchor; + + // When set to YES, lazily create the edge node, and keep it around when set + // to NO (at least until saved/loaded). + BOOL hasEdgeNode; + BOOL dirty; + + // these are all cached values computed from the above + NSPoint src; + NSPoint targ; + NSPoint head; + NSPoint tail; + NSPoint cp1; + NSPoint cp2; + NSPoint mid; + NSPoint midTan; + NSPoint headTan; + NSPoint tailTan; +} + +/*! + @property data + @brief Associated edge data. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @property style + @brief Edge style. + */ +@property (retain) EdgeStyle *style; + +/*! + @property source + @brief Source node. + */ +@property (retain) Node *source; + +/*! + @property target + @brief Target node. + */ +@property (retain) Node *target; + +/*! + @property edgeNode + @brief A node attached to this edge, as in a label or tick. + */ +@property (retain) Node *edgeNode; + +/*! + @property sourceAnchor + @brief The source node anchor point, as in north or center. + */ +@property (copy) NSString *sourceAnchor; + +/*! + @property targetAnchor + @brief The target node anchor point, as in north or center. + */ +@property (copy) NSString *targetAnchor; + +/*! + @property hasEdgeNode + @brief A read/write property. When set to true, a new edge node is actually constructed. +*/ +@property (assign) BOOL hasEdgeNode; + +/*! + @property bend + @brief The degrees by which the edge bends. + */ +@property (assign) int bend; + +/*! + @property weight + @brief How close the edge will pass to control points. + */ +@property (assign) float weight; + +/*! + @property inAngle + @brief The angle by which the edge enters its target. + */ +@property (assign) int inAngle; + +/*! + @property outAngle + @brief The angle by which the edge leaves its target. + */ +@property (assign) int outAngle; + +/*! + @property bendMode + @brief The mode of the edge bend. Either simple bend in in/out style. + */ +@property (assign) EdgeBendMode bendMode; + +/*! + @property head + @brief The starting point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the source node. + */ +@property (readonly) NSPoint head; + +/*! + @property tail + @brief The ending point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the target node. + */ +@property (readonly) NSPoint tail; + +/*! + @property cp1 + @brief The first control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. + */ +@property (readonly) NSPoint cp1; + +/*! + @property cp2 + @brief The second control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. + */ +@property (readonly) NSPoint cp2; + +/*! + @property mid + @brief The midpoint of the curve. Computed from the source, target, and control points. + */ +@property (readonly) NSPoint mid; + +/*! + @property mid_tan + @brief The second point of a line tangent to the midpoint. (The first is the midpoint itself.) + */ +@property (readonly) NSPoint midTan; + +/*! + @property left_normal + @brief The second point in a line perp. to the edge coming from mid-point. (left side) + */ +@property (readonly) NSPoint leftNormal; + +/*! + @property left_normal + @brief The second point in a line perp. to the edge coming from mid-point. (right side) + */ +@property (readonly) NSPoint rightNormal; + +@property (readonly) NSPoint headTan; + +/*! + @property leftHeadNormal + */ +@property (readonly) NSPoint leftHeadNormal; + +/*! + @property rightHeadNormal + */ +@property (readonly) NSPoint rightHeadNormal; + +@property (readonly) NSPoint tailTan; + +/*! + @property leftTailNormal + */ +@property (readonly) NSPoint leftTailNormal; + +/*! + @property rightTailNormal + */ +@property (readonly) NSPoint rightTailNormal; + +/*! + @property isSelfLoop + @brief Returns YES if this edge is a self loop. + */ +@property (readonly) BOOL isSelfLoop; + +/*! + @property isStraight + @brief Returns YES if this edge can be drawn as a straight line (as opposed to a bezier curve). + */ +@property (readonly) BOOL isStraight; + + +/*! + @brief Construct a blank edge. + @result An edge. + */ +- (id)init; + +/*! + @brief Construct an edge with the given source and target. + @param s the source node. + @param t the target node. + @result An edge. + */ +- (id)initWithSource:(Node*)s andTarget:(Node*)t; + +/*! + @brief Force the recalculation of the derived properties. + */ +- (void)recalculateProperties; + +/*! + @brief Recompute the control points and midpoint. + */ +- (void)updateControls; + +/*! + @brief Push edge properties back into its <tt>GraphElementData</tt>. + */ +- (void)updateData; + +/*! + @brief Set edge properties from fields in <tt>GraphElementData</tt>. + */ +- (void)setAttributesFromData; + +/*! + @brief Use data.style to find and attach the <tt>EdgeStyle</tt> object from the given array. + */ +- (BOOL)attachStyleFromTable:(NSArray*)styles; + +/*! + @brief Convert the bend angle to an inAngle and outAngle. + */ +- (void)convertBendToAngles; + +/*! + @brief Set the bend angle to the average of the in and out angles. + */ +- (void)convertAnglesToBend; + +/*! + @brief Update this edge to look just like the given edge. + @param e an edge to mimic. + */ +- (void)setPropertiesFromEdge:(Edge *)e; + +/*! + @brief Get a bounding rect for this edge. + @detail Note that this may not be a tight bound. + */ +- (NSRect)boundingRect; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or outAngle) to fit. + + A courseness can be specified for both the weight and the + bend, allowing them to be constrained to certain values. For + example, passing 10 as the bend courseness will force the bend + to be a multiple of 5. Passing 0 for either of these will + cause the relevant value to be unconstrained. + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or outAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp1To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or outAngle) to fit. + + The same as moveCp1To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:No + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or outAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp1To:(NSPoint)point; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or inAngle) to fit. + + A courseness can be specified for both the weight and the + bend, allowing them to be constrained to certain values. For + example, passing 10 as the bend courseness will force the bend + to be a multiple of 5. Passing 0 for either of these will + cause the relevant value to be unconstrained. + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or inAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp2To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or inAngle) to fit. + + The same as moveCp2To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:No + @param point the new position of the control point + */ +- (void) moveCp2To:(NSPoint)point; + +/*! + @brief Reverse edge direction, updating bend/inAngle/outAngle/etc + */ +- (void)reverse; + +/*! + @brief Factory method to make a blank edge. + @result An edge. + */ ++ (Edge*)edge; + +/*! + @brief Factory method to make an edge with the given source and target. + @param s a source node. + @param t a target node. + @result An edge. + */ ++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Edge.m b/tikzit-1/src/common/Edge.m new file mode 100644 index 0000000..0c88e9d --- /dev/null +++ b/tikzit-1/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 diff --git a/tikzit-1/src/common/EdgeStyle.h b/tikzit-1/src/common/EdgeStyle.h new file mode 100644 index 0000000..a51f129 --- /dev/null +++ b/tikzit-1/src/common/EdgeStyle.h @@ -0,0 +1,71 @@ +// +// EdgeStyle.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "PropertyHolder.h" +#import "ColorRGB.h" + +typedef enum { + AH_None = 0, + AH_Plain = 1, + AH_Latex = 2 +} ArrowHeadStyle; + +typedef enum { + ED_None = 0, + ED_Arrow = 1, + ED_Tick = 2 +} EdgeDectorationStyle; + +@interface EdgeStyle : PropertyHolder <NSCopying> { + ArrowHeadStyle headStyle, tailStyle; + EdgeDectorationStyle decorationStyle; + float thickness; + ColorRGB *colorRGB; + NSString *name; + NSString *category; +} + +/*! + @property colorRGB + @brief The color to render the line in + */ +@property (copy) ColorRGB *colorRGB; + +@property (copy) NSString *name; +@property (copy) NSString *category; +@property (assign) ArrowHeadStyle headStyle; +@property (assign) ArrowHeadStyle tailStyle; +@property (assign) EdgeDectorationStyle decorationStyle; +@property (assign) float thickness; + +@property (readonly) NSString *tikz; + +- (id)init; +- (id)initWithName:(NSString*)nm; ++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm; +- (void) updateFromStyle:(EdgeStyle*)style; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/EdgeStyle.m b/tikzit-1/src/common/EdgeStyle.m new file mode 100644 index 0000000..c61e94a --- /dev/null +++ b/tikzit-1/src/common/EdgeStyle.m @@ -0,0 +1,222 @@ +// +// EdgeStyle.m +// TikZiT +// +// Copyright 2011 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 "EdgeStyle.h" + +@implementation EdgeStyle + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObjects: + @"tailStyle", + @"headStyle", + @"decorationStyle", + @"thickness", + @"colorRGB.red", + @"colorRGB.blue", + @"colorRGB.green", + @"name", + nil] + triggerChangeNotificationsForDependentKey:@"tikz"]; + [self setKeys:[NSArray arrayWithObjects: + @"colorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"colorIsKnown"]; +} + +- (id)initWithName:(NSString*)nm { + self = [super initWithNotificationName:@"EdgeStylePropertyChanged"]; + + if (self != nil) { + headStyle = AH_None; + tailStyle = AH_None; + decorationStyle = ED_None; + colorRGB = [[ColorRGB alloc] initWithRed:0 green:0 blue:0]; + name = nm; + category = nil; + thickness = 1.0f; + } + + return self; +} + +- (id)init { + self = [self initWithName:@"new"]; + return self; +} + +- (id)copyWithZone:(NSZone*)zone { + EdgeStyle *style = [[EdgeStyle allocWithZone:zone] init]; + [style setName:[self name]]; + [style setCategory:[self category]]; + [style setHeadStyle:[self headStyle]]; + [style setTailStyle:[self tailStyle]]; + [style setDecorationStyle:[self decorationStyle]]; + [style setThickness:[self thickness]]; + [style setColorRGB:[self colorRGB]]; + return style; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [category release]; + [colorRGB release]; + [super dealloc]; +#endif +} + +- (NSString*) description { + return [NSString stringWithFormat:@"Edge style \"%@\"", name]; +} + +- (void) updateFromStyle:(EdgeStyle*)style { + [self setName:[style name]]; + [self setCategory:[style category]]; + [self setHeadStyle:[style headStyle]]; + [self setTailStyle:[style tailStyle]]; + [self setDecorationStyle:[style decorationStyle]]; + [self setThickness:[style thickness]]; + [self setColorRGB:[style colorRGB]]; +} + ++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm { +#if __has_feature(objc_arc) + return [[EdgeStyle alloc] initWithName:nm]; +#else + return [[[EdgeStyle alloc] initWithName:nm] autorelease]; +#endif +} + +- (NSString*)name { return name; } +- (void)setName:(NSString *)s { + if (name != s) { + NSString *oldValue = name; + name = [s copy]; + [self postPropertyChanged:@"name" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ArrowHeadStyle)headStyle { return headStyle; } +- (void)setHeadStyle:(ArrowHeadStyle)s { + ArrowHeadStyle oldValue = headStyle; + headStyle = s; + [self postPropertyChanged:@"headStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (ArrowHeadStyle)tailStyle { return tailStyle; } +- (void)setTailStyle:(ArrowHeadStyle)s { + ArrowHeadStyle oldValue = tailStyle; + tailStyle = s; + [self postPropertyChanged:@"tailStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (EdgeDectorationStyle)decorationStyle { return decorationStyle; } +- (void)setDecorationStyle:(EdgeDectorationStyle)s { + EdgeDectorationStyle oldValue = decorationStyle; + decorationStyle = s; + [self postPropertyChanged:@"decorationStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} +- (float)thickness { return thickness; } +- (void)setThickness:(float)s { + float oldValue = thickness; + thickness = s; + [self postPropertyChanged:@"thickness" oldValue:[NSNumber numberWithFloat:oldValue]]; +} + +- (NSString*)category { + return category; +} + +- (void)setCategory:(NSString *)s { + if (category != s) { + NSString *oldValue = category; + category = [s copy]; + [self postPropertyChanged:@"category" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ColorRGB*)colorRGB { + return colorRGB; +} + +- (void)setColorRGB:(ColorRGB*)c { + if (colorRGB != c) { + ColorRGB *oldValue = colorRGB; + colorRGB = [c copy]; + [self postPropertyChanged:@"colorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)tikz { + NSMutableString *buf = [NSMutableString stringWithFormat:@"\\tikzstyle{%@}=[", name]; + + NSString *colorName = [colorRGB name]; + if (colorName == nil) + colorName = [colorRGB hexName]; + + if (tailStyle == AH_Plain) + [buf appendString:@"<"]; + else if (tailStyle == AH_Latex) + [buf appendString:@"latex"]; + + [buf appendString:@"-"]; + + if (headStyle == AH_Plain) + [buf appendString:@">"]; + else if (headStyle == AH_Latex) + [buf appendString:@"latex"]; + + if(colorName != nil){ + [buf appendString:@",draw="]; + [buf appendString:colorName]; + } + + if (decorationStyle != ED_None) { + [buf appendString:@",postaction={decorate},decoration={markings,mark="]; + if (decorationStyle == ED_Arrow) + [buf appendString:@"at position .5 with {\\arrow{>}}"]; + else if (decorationStyle == ED_Tick) + [buf appendString:@"at position .5 with {\\draw (0,-0.1) -- (0,0.1);}"]; + [buf appendString:@"}"]; + } + + if (thickness != 1.0f) { + [buf appendFormat:@",line width=%.3f", thickness]; + } + + [buf appendString:@"]"]; + return buf; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Graph.h b/tikzit-1/src/common/Graph.h new file mode 100644 index 0000000..1f98858 --- /dev/null +++ b/tikzit-1/src/common/Graph.h @@ -0,0 +1,401 @@ +// +// Graph.h +// 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/>. +// + + +/*! + + @mainpage TikZiT + TikZiT is a GUI application for drawing, editing, and parsing TikZ + diagrams. Common code is in src/common, and plaform-specific code is + in src/{osx,linux}. + + */ + + +#import "Node.h" +#import "Edge.h" +#import "GraphChange.h" +#import "Transformer.h" + +/*! + @class Graph + @brief Store a graph, output to TikZ. + @details All of the methods that change a graph return an object of type GraphChange. + Graph changes can be re-done by calling applyGraphChange. They can be undone + by calling applyGraphChange on [change inverse]. + */ +@interface Graph : NSObject <NSCopying> { + NSRecursiveLock *graphLock; + BOOL dirty; // keep track of when inEdges and outEdges need an update + NSMutableArray *nodes; + NSMutableArray *edges; + + NSMapTable *inEdges; + NSMapTable *outEdges; + + GraphElementData *data; + NSRect boundingBox; +} + +/*! + @property data + @brief Data associated with the graph. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @property nodes + @brief The set of nodes. + @details The node set is cached internally, so no need to lock + the graph when enumerating. + */ +@property (readonly) NSArray *nodes; + +/*! + @property edges + @brief The set of edges. + @details The edge set is cached internally, so no need to lock + the graph when enumerating. + */ +@property (readonly) NSArray *edges; + +/*! + @property boundingBox + @brief The bounding box of a graph + @details Optional data containing the bounding box, set with + \path [use as bounding box] .... + */ +@property (assign) NSRect boundingBox; + +/*! + @property hasBoundingBox + @brief Returns true if this graph has a bounding box. + */ +@property (readonly) BOOL hasBoundingBox; + + +/*! + @brief Computes graph bounds. + @result Graph bounds. + */ +- (NSRect)bounds; + +/*! + @brief Returns the set of edges incident to the given node set. + @param nds a set of nodes. + @result A set of incident edges. + */ +- (NSSet*)incidentEdgesForNodes:(NSSet*)nds; + +/*! + @brief Returns the set of in-edges for this node. + @param nd a node. + @result A set of edges. +*/ +- (NSSet*)inEdgesForNode:(Node*)nd; + +/*! + @brief Returns the set of out-edges for this node. + @param nd a node. + @result A set of edges. +*/ +- (NSSet*)outEdgesForNode:(Node*)nd; + +/*! + @brief Gives a copy of the full subgraph with the given nodes. + @param nds a set of nodes. + @result A subgraph. + */ +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds; + +/*! + @brief Gives a copy of the full subgraph with the given nodes. + @param nds a set of nodes. + @param zone an allocation zone + @result A subgraph. + */ +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds zone:(NSZone*)zone; + +/*! + @brief Gives a set of edge-arrays that partition all of the edges in the graph. + @result An NSet of NSArrays of edges. + */ +- (NSSet*)pathCover; + +/*! + @brief Adds a node. + @param node + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addNode:(Node*)node; + +/*! + @brief Removes a node. + @param node + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeNode:(Node*)node; + +/*! + @brief Removes a set of nodes. + @param nds a set of nodes + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeNodes:(NSSet *)nds; + +/*! + @brief Adds an edge. + @param edge + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addEdge:(Edge*)edge; + +/*! + @brief Removed an edge. + @param edge + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeEdge:(Edge*)edge; + +/*! + @brief Removes a set of edges. + @param es a set of edges. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeEdges:(NSSet *)es; + +/*! + @brief Convenience function, intializes an edge with the given + source and target and adds it. + @param source the source of the edge. + @param target the target of the edge. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addEdgeFrom:(Node*)source to:(Node*)target; + +/*! + @brief Return the z-index for a given node (lower is farther back). + @param node a node in the graph + @result An <tt>int</tt> + */ +- (int)indexOfNode:(Node*)node; + +/*! + @brief Set the z-index for a given node (lower is farther back). + @param idx a new z-index + @param node a node in the graph + */ +- (void)setIndex:(int)idx ofNode:(Node*)node; + +/*! + @brief Bring set of nodes forward by one. + @param nodeSet a set of nodes + */ +- (GraphChange*)bringNodesForward:(NSSet*)nodeSet; + +/*! + @brief Bring set of nodes to the front. + @param nodeSet a set of nodes + */ +- (GraphChange*)bringNodesToFront:(NSSet*)nodeSet; + +/*! + @brief Bring set of edges to the front. + @param edgeSet a set of edges + */ +- (GraphChange*)bringEdgesToFront:(NSSet*)edgeSet; + +/*! + @brief Bring set of edges forward by one. + @param edgeSet a set of edges + */ +- (GraphChange*)bringEdgesForward:(NSSet*)edgeSet; + +/*! + @brief Send set of nodes backward by one. + @param nodeSet a set of nodes + */ +- (GraphChange*)sendNodesBackward:(NSSet*)nodeSet; + +/*! + @brief Send set of edges backward by one. + @param edgeSet a set of edges + */ +- (GraphChange*)sendEdgesBackward:(NSSet*)edgeSet; + +/*! + @brief Send set of nodes to back. + @param nodeSet a set of nodes + */ +- (GraphChange*)sendNodesToBack:(NSSet*)nodeSet; + +/*! + @brief Send set of edges to back. + @param edgeSet a set of edges + */ +- (GraphChange*)sendEdgesToBack:(NSSet*)edgeSet; + + +/*! + @brief Transform every node in the graph to screen space. + @param t a transformer + */ +- (void)applyTransformer:(Transformer*)t; + +/*! + @brief Shift nodes by a given distance. + @param ns a set of nodes. + @param p an x and y distance, given as an NSPoint. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)shiftNodes:(id<NSFastEnumeration>)ns byPoint:(NSPoint)p; + +/*! + @brief Reverse the given edges + @param es the edges to reverse + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)reverseEdges:(NSSet *)es; + +/*! + @brief Insert the given graph into this one. Used for copy + and paste. + @param g a graph. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)insertGraph:(Graph*)g; + +/*! + @brief Flip the subgraph defined by the given node set + horizontally. + @param nds a set of nodes. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)flipHorizontalNodes:(NSSet*)nds; + +/*! + @brief Flip the subgraph defined by the given node set + vertically. + @param nds a set of nodes. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)flipVerticalNodes:(NSSet*)nds; + +/*! + @brief Apply a graph change. + @details An undo manager should maintain a stack of GraphChange + objects returned. To undo a GraphChange, call this method + with <tt>[change inverse]</tt> as is argument. + @param ch a graph change. + */ +- (void)applyGraphChange:(GraphChange*)ch; + +/*! + @brief The TikZ representation of this graph. + @details The TikZ representation of this graph. The TikZ code should + contain enough data to totally reconstruct the graph. + @result A string containing TikZ code. + */ +- (NSString*)tikz; + + +/*! + @brief Copy the node set and return a table of copies, whose + keys are the original nodes. This is used to save the state + of a set of nodes in a GraphChange. + @param nds a set of nodes. + @result A <tt>NSMapTable</tt> of node copies. + */ ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds; + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds withZone:(NSZone*)zone; + +/*! + @brief Copy the edge set and return a table of copies, whose + keys are the original edges. This is used to save the state + of a set of edges in a GraphChange. + @param es a set of edges. + @result A <tt>NSMapTable</tt> of edge copies. + */ ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es; + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es withZone:(NSZone*)zone; + +/*! + @brief Compute the bounds for a set of nodes. + @param nds an enumerable collection of nodes. + @result The bounds. + */ ++ (NSRect)boundsForNodes:(id<NSFastEnumeration>)nds; + +/*! + @brief Factory method for constructing graphs. + @result An empty graph. + */ ++ (Graph*)graph; + +/** + * Initialize an empty graph + */ +- (id)init; + +/** + * Constructs a graph from the given tikz code + * + * See TikzGraphAssembler for more information about the error argument. + */ ++ (Graph*)graphFromTikz:(NSString*)tikz error:(NSError**)e; + +/** + * Constructs a graph from the given tikz code + */ ++ (Graph*)graphFromTikz:(NSString*)tikz; + +/** + * Initialize an empty graph from the given tikz code + * + * Note that this may not return the same object it was called on, + * and will return nil if parsing failed. + * + * See TikzGraphAssembler for more information about the error argument. + */ +- (id)initFromTikz:(NSString*)tikz error:(NSError**)e; + +/** + * Initialize an empty graph from the given tikz code + * + * Note that this may not return the same object it was called on, + * and will return nil if parsing failed. + */ +- (id)initFromTikz:(NSString*)tikz; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Graph.m b/tikzit-1/src/common/Graph.m new file mode 100644 index 0000000..cf09a69 --- /dev/null +++ b/tikzit-1/src/common/Graph.m @@ -0,0 +1,922 @@ +// +// Graph.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 "Graph.h" +#import "TikzGraphAssembler.h" +#import "Shape.h" + +@interface Graph (Private) +- (void) shapeDictionaryReplaced:(NSNotification*)notification; +@end + +@implementation Graph + +- (id)init { + self = [super init]; + if (self != nil) { + data = [[GraphElementData alloc] init]; + boundingBox = NSMakeRect(0, 0, 0, 0); + graphLock = [[NSRecursiveLock alloc] init]; + nodes = [[NSMutableArray alloc] initWithCapacity:10]; + edges = [[NSMutableArray alloc] initWithCapacity:10]; + inEdges = nil; + outEdges = nil; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + return self; +} + +- (id)initFromTikz:(NSString*)tikz error:(NSError**)e { +#if __has_feature(objc_arc) + return [TikzGraphAssembler parseTikz:tikz error:e]; +#else + [self release]; + return [[TikzGraphAssembler parseTikz:tikz error:e] retain]; +#endif +} + +- (id)initFromTikz:(NSString*)tikz { + return [self initFromTikz:tikz error:NULL]; +} + +- (id) copyWithZone:(NSZone*)zone { + Graph *newGraph = [self copyOfSubgraphWithNodes:[NSSet setWithArray:nodes] zone:zone]; + [newGraph setData:[self data]]; + return newGraph; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [graphLock lock]; +#if ! __has_feature(objc_arc) + [inEdges release]; + [outEdges release]; + [edges release]; + [nodes release]; + [data release]; + [graphLock unlock]; + [graphLock release]; + + [super dealloc]; +#endif +} + +- (void)sync { + [graphLock lock]; + if (dirty) { +#if ! __has_feature(objc_arc) + [inEdges release]; + [outEdges release]; +#endif + inEdges = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:10]; + outEdges = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:10]; + +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + + for (Edge *e in edges) { + NSMutableSet *ie = [inEdges objectForKey:[e target]]; + NSMutableSet *oe = [outEdges objectForKey:[e source]]; + + if (ie == nil) { + ie = [NSMutableSet setWithCapacity:4]; + [inEdges setObject:ie forKey:[e target]]; + } + + if (oe == nil) { + oe = [NSMutableSet setWithCapacity:4]; + [outEdges setObject:oe forKey:[e source]]; + } + + [ie addObject:e]; + [oe addObject:e]; + } + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + + dirty = NO; + } + [graphLock unlock]; +} + +@synthesize nodes; +@synthesize edges; + +@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]; +} + +@synthesize boundingBox; + +- (NSRect)bounds { + [graphLock lock]; + NSRect b = [Graph boundsForNodes:nodes]; + [graphLock unlock]; + return b; +} + +- (BOOL)hasBoundingBox { + return !( + boundingBox.size.width == 0 && + boundingBox.size.height == 0 + ); +} + +- (NSSet*)inEdgesForNode:(Node*)nd { + [self sync]; +#if __has_feature(objc_arc) + return [inEdges objectForKey:nd]; +#else + return [[[inEdges objectForKey:nd] retain] autorelease]; +#endif +} + +- (NSSet*)outEdgesForNode:(Node*)nd { + [self sync]; +#if __has_feature(objc_arc) + return [outEdges objectForKey:nd]; +#else + return [[[outEdges objectForKey:nd] retain] autorelease]; +#endif +} + +- (NSSet*)incidentEdgesForNodes:(NSSet*)nds { + [self sync]; + + NSMutableSet *mset = [NSMutableSet setWithCapacity:10]; + for (Node *n in nds) { + [mset unionSet:[self inEdgesForNode:n]]; + [mset unionSet:[self outEdgesForNode:n]]; + } + + return mset; +} + +- (void)applyTransformer:(Transformer *)t { + [graphLock lock]; + for (Node *n in nodes) { + [n setPoint:[t toScreen:[n point]]]; + } + [graphLock unlock]; +} + +- (GraphChange*)addNode:(Node *)node{ + [graphLock lock]; + NSSet *addedNode; + + // addNode is a no-op if graph already contains node + if (![nodes containsObject:node]) { + [nodes addObject:node]; + dirty = YES; + addedNode = [NSSet setWithObject:node]; + } else { + addedNode = [NSSet set]; + } + [graphLock unlock]; + + return [GraphChange graphAdditionWithNodes:addedNode + edges:[NSSet set]]; +} + +- (GraphChange*)removeNode:(Node*)node { + [graphLock lock]; + NSMutableSet *affectedEdges = [NSMutableSet set]; + for (Edge *e in edges) { + if ([e source] == node || [e target] == node) { + [affectedEdges addObject:e]; + } + } + for (Edge *e in affectedEdges) { + [edges removeObject:e]; + } + [nodes removeObject:node]; + dirty = YES; + [graphLock unlock]; + + return [GraphChange graphDeletionWithNodes:[NSSet setWithObject:node] + edges:affectedEdges]; +} + +- (GraphChange*)removeNodes:(NSSet *)nds { + [graphLock lock]; + + Node *n; + Edge *e; + + NSMutableSet *affectedEdges = [NSMutableSet set]; + NSEnumerator *en = [edges objectEnumerator]; + while ((e = [en nextObject])) { + if ([nds containsObject:[e source]] || [nds containsObject:[e target]]) { + [affectedEdges addObject:e]; + } + } + + en = [affectedEdges objectEnumerator]; + while ((e = [en nextObject])) [edges removeObject:e]; + + en = [nds objectEnumerator]; + while ((n = [en nextObject])) [nodes removeObject:n]; + + dirty = YES; + [graphLock unlock]; + + return [GraphChange graphDeletionWithNodes:nds edges:affectedEdges]; +} + +- (GraphChange*)addEdge:(Edge*)edge { + [graphLock lock]; + NSSet *addedEdge; + + // addEdge is a no-op if graph already contains edge + if (![edges containsObject:edge]) { + [edges addObject:edge]; + dirty = YES; + addedEdge = [NSSet setWithObject:edge]; + } else { + addedEdge = [NSSet set]; + } + [graphLock unlock]; + + return [GraphChange graphAdditionWithNodes:[NSSet set] + edges:addedEdge]; +} + +- (GraphChange*)removeEdge:(Edge *)edge { + [graphLock lock]; + [edges removeObject:edge]; + dirty = YES; + [graphLock unlock]; + return [GraphChange graphDeletionWithNodes:[NSSet set] + edges:[NSSet setWithObject:edge]]; +} + +- (GraphChange*)removeEdges:(NSSet *)es { + [graphLock lock]; + + for (Edge *e in es) { + [edges removeObject:e]; + } + dirty = YES; + [graphLock unlock]; + return [GraphChange graphDeletionWithNodes:[NSSet set] edges:es]; +} + +- (GraphChange*)addEdgeFrom:(Node *)source to:(Node *)target { + return [self addEdge:[Edge edgeWithSource:source andTarget:target]]; +} + +- (GraphChange*)shiftNodes:(id<NSFastEnumeration>)ns byPoint:(NSPoint)p { + NSPoint newLoc; + NSMutableSet *nodeSet = [NSMutableSet setWithCapacity:5]; + for (Node *n in ns) { + newLoc = NSMakePoint([n point].x + p.x, [n point].y + p.y); + [n setPoint:newLoc]; + [nodeSet addObject:n]; + } + return [GraphChange shiftNodes:nodeSet byPoint:p]; +} + +- (GraphChange*)reverseEdges:(NSSet *)es { + [graphLock lock]; + for (Edge *e in es) { + [e reverse]; + } + dirty = YES; + [graphLock unlock]; + return [GraphChange reverseEdges:es]; +} + +- (int)indexOfNode:(Node *)node { + return [nodes indexOfObject:node]; +} + +- (void)setIndex:(int)idx ofNode:(Node *)node { + [graphLock lock]; + + if ([nodes containsObject:node]) { + [nodes removeObject:node]; + [nodes insertObject:node atIndex:idx]; + } + + [graphLock unlock]; +} + +- (int)indexOfEdge:(Edge *)edge { + return [edges indexOfObject:edge]; +} + +- (void)setIndex:(int)idx ofEdge:(Edge *)edge { + [graphLock lock]; + + if ([edges containsObject:edge]) { + [edges removeObject:edge]; + [edges insertObject:edge atIndex:idx]; + } + + [graphLock unlock]; +} + +- (GraphChange*)bringNodesForward:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + [graphLock lock]; + // start at the top of the array and work backwards + for (int i = [nodes count]-2; i >= 0; --i) { + if ( [nodeSet containsObject:[nodes objectAtIndex:i]] && + ![nodeSet containsObject:[nodes objectAtIndex:i+1]]) + { + [self setIndex:(i+1) ofNode:[nodes objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringNodesToFront:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + int i = 0, top = [nodes count]-1; + + while (i <= top) { + if ([nodeSet containsObject:[nodes objectAtIndex:i]]) { + [self setIndex:([nodes count]-1) ofNode:[nodes objectAtIndex:i]]; + --top; + } else { + ++i; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringEdgesForward:(NSSet*)edgeSet { + [graphLock lock]; + NSArray *oldOrder = [edges copy]; + // start at the top of the array and work backwards + for (int i = [edges count]-2; i >= 0; --i) { + if ( [edgeSet containsObject:[edges objectAtIndex:i]] && + ![edgeSet containsObject:[edges objectAtIndex:i+1]]) + { + [self setIndex:(i+1) ofEdge:[edges objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringEdgesToFront:(NSSet*)edgeSet { + NSArray *oldOrder = [edges copy]; + int i = 0, top = [edges count]-1; + + while (i <= top) { + if ([edgeSet containsObject:[edges objectAtIndex:i]]) { + [self setIndex:([edges count]-1) ofEdge:[edges objectAtIndex:i]]; + --top; + } else { + ++i; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendNodesBackward:(NSSet*)nodeSet { + [graphLock lock]; + NSArray *oldOrder = [nodes copy]; + // start at the top of the array and work backwards + for (int i = 1; i < [nodes count]; ++i) { + if ( [nodeSet containsObject:[nodes objectAtIndex:i]] && + ![nodeSet containsObject:[nodes objectAtIndex:i-1]]) + { + [self setIndex:(i-1) ofNode:[nodes objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendEdgesBackward:(NSSet*)edgeSet { + [graphLock lock]; + NSArray *oldOrder = [edges copy]; + // start at the top of the array and work backwards + for (int i = 1; i < [edges count]; ++i) { + if ( [edgeSet containsObject:[edges objectAtIndex:i]] && + ![edgeSet containsObject:[edges objectAtIndex:i-1]]) + { + [self setIndex:(i-1) ofEdge:[edges objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendNodesToBack:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + int i = [nodes count]-1, bot = 0; + + while (i >= bot) { + if ([nodeSet containsObject:[nodes objectAtIndex:i]]) { + [self setIndex:0 ofNode:[nodes objectAtIndex:i]]; + ++bot; + } else { + --i; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendEdgesToBack:(NSSet*)edgeSet { + NSArray *oldOrder = [edges copy]; + int i = [edges count]-1, bot = 0; + + while (i >= bot) { + if ([edgeSet containsObject:[edges objectAtIndex:i]]) { + [self setIndex:0 ofEdge:[edges objectAtIndex:i]]; + ++bot; + } else { + --i; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)insertGraph:(Graph*)g { + [graphLock lock]; + + for (Node *n in [g nodes]) { + [self addNode:n]; + } + + for (Edge *e in [g edges]) { + [self addEdge:e]; + } + + dirty = YES; + + [graphLock unlock]; + + + return [GraphChange graphAdditionWithNodes:[NSSet setWithArray:[g nodes]] edges:[NSSet setWithArray:[g edges]]]; +} + +- (void)flipNodes:(NSSet*)nds horizontal:(BOOL)horiz { + [graphLock lock]; + + NSRect bds = [Graph boundsForNodes:nds]; + float ctr; + if (horiz) ctr = bds.origin.x + (bds.size.width/2); + else ctr = bds.origin.y + (bds.size.height/2); + + Node *n; + NSPoint p; + NSEnumerator *en = [nds objectEnumerator]; + while ((n = [en nextObject])) { + p = [n point]; + if (horiz) p.x = 2 * ctr - p.x; + else p.y = 2 * ctr - p.y; + [n setPoint:p]; + } + + Edge *e; + en = [edges objectEnumerator]; + while ((e = [en nextObject])) { + if ([nds containsObject:[e source]] && + [nds containsObject:[e target]]) + { + if ([e bendMode] == EdgeBendModeInOut) { + if (horiz) { + if ([e inAngle] < 0) [e setInAngle:(-180 - [e inAngle])]; + else [e setInAngle:180 - [e inAngle]]; + + if ([e outAngle] < 0) [e setOutAngle:(-180 - [e outAngle])]; + else [e setOutAngle:180 - [e outAngle]]; + } else { + [e setInAngle:-[e inAngle]]; + [e setOutAngle:-[e outAngle]]; + } + } else { + [e setBend:-[e bend]]; + } + } + } + + [graphLock unlock]; +} + +- (GraphChange*)flipHorizontalNodes:(NSSet*)nds { + [self flipNodes:nds horizontal:YES]; + return [GraphChange flipNodes:nds horizontal:YES]; +} + +- (GraphChange*)flipVerticalNodes:(NSSet*)nds { + [self flipNodes:nds horizontal:NO]; + return [GraphChange flipNodes:nds horizontal:NO]; +} + +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds { + return [self copyOfSubgraphWithNodes:nds zone:NSDefaultMallocZone()]; +} + +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds zone:(NSZone*)zone { + [graphLock lock]; + + NSMapTable *newNds = [Graph nodeTableForNodes:nds withZone:zone]; + Graph* newGraph = [[Graph allocWithZone:zone] init]; + + for (Node *nd in [newNds objectEnumerator]) { + [newGraph addNode:nd]; + } + + for (Edge *e in edges) { + if ([nds containsObject:[e source]] && [nds containsObject:[e target]]) { + Edge *e1 = [e copyWithZone:zone]; + [e1 setSource:[newNds objectForKey:[e source]]]; + [e1 setTarget:[newNds objectForKey:[e target]]]; + [newGraph addEdge:e1]; +#if ! __has_feature(objc_arc) + [e1 release]; // e1 belongs to newGraph +#endif + } + } + + [graphLock unlock]; + + return newGraph; +} + +- (NSSet*)pathCover { + [self sync]; + + NSMutableSet *cover = [NSMutableSet set]; +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + NSMutableSet *remainingEdges = [NSMutableSet setWithArray:edges]; + + while ([remainingEdges count] != 0) { + NSMutableArray *path = [[NSMutableArray alloc] init]; + NSSet *succs; + Edge *succ; + NSEnumerator *en; + + Edge *e = [remainingEdges anyObject]; + + while (e!=nil) { + [path addObject:e]; + [remainingEdges removeObject:e]; + + succs = [self outEdgesForNode:[e target]]; + en = [succs objectEnumerator]; + e = nil; + + while ((succ = [en nextObject])) { + if ([remainingEdges containsObject:succ]) e = succ; + } + } + + [cover addObject:path]; +#if ! __has_feature(objc_arc) + [path release]; +#endif + } + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + return cover; +} + +- (void)applyGraphChange:(GraphChange*)ch { + [graphLock lock]; + switch ([ch changeType]) { + case GraphAddition: + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [nodes addObject:n]; + } + + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [edges addObject:e]; + } + + break; + case GraphDeletion: + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [edges removeObject:e]; + } + + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [nodes removeObject:n]; + } + + break; + case NodePropertyChange: + [[ch nodeRef] setPropertiesFromNode:[ch nwNode]]; + break; + case NodesPropertyChange: + for (Node *key in [[ch nwNodeTable] keyEnumerator]) { + [key setPropertiesFromNode:[[ch nwNodeTable] objectForKey:key]]; + } + break; + case EdgePropertyChange: + [[ch edgeRef] setPropertiesFromEdge:[ch nwEdge]]; + break; + case EdgesPropertyChange: + for (Edge *key in [[ch nwEdgeTable] keyEnumerator]) { + [key setPropertiesFromEdge:[[ch nwEdgeTable] objectForKey:key]]; + } + break; + case NodesShift: + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [n setPoint:NSMakePoint([n point].x + [ch shiftPoint].x, + [n point].y + [ch shiftPoint].y)]; + } + break; + case NodesFlip: + [self flipNodes:[ch affectedNodes] horizontal:[ch horizontal]]; + break; + case EdgesReverse: + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [e reverse]; + } + break; + case BoundingBoxChange: + [self setBoundingBox:[ch nwBoundingBox]]; + break; + case GraphPropertyChange: + [data setArray:[ch nwGraphData]]; + break; + case NodeOrderChange: + [nodes setArray:[ch nwNodeOrder]]; + break; + case EdgeOrderChange: + [edges setArray:[ch nwEdgeOrder]]; + break; + } + + dirty = YES; + [graphLock unlock]; +} + +//- (void)undoGraphChange:(GraphChange*)ch { +// [self applyGraphChange:[GraphChange inverseGraphChange:ch]]; +//} + +- (NSString*)tikz { + [graphLock lock]; + + NSMutableString *code = [NSMutableString + stringWithFormat:@"\\begin{tikzpicture}%@\n", + [[self data] tikzList]]; + + if ([self hasBoundingBox]) { + [code appendFormat:@"\t\\path [use as bounding box] (%@,%@) rectangle (%@,%@);\n", + [NSNumber numberWithFloat:boundingBox.origin.x], + [NSNumber numberWithFloat:boundingBox.origin.y], + [NSNumber numberWithFloat:boundingBox.origin.x + boundingBox.size.width], + [NSNumber numberWithFloat:boundingBox.origin.y + boundingBox.size.height]]; + } + +// NSArray *sortedNodeList = [[nodes allObjects] +// sortedArrayUsingSelector:@selector(compareTo:)]; + //NSMutableDictionary *nodeNames = [NSMutableDictionary dictionary]; + + if ([nodes count] > 0) [code appendFormat:@"\t\\begin{pgfonlayer}{nodelayer}\n"]; + + int i = 0; + for (Node *n in nodes) { + [n updateData]; + [n setName:[NSString stringWithFormat:@"%d", i]]; + [code appendFormat:@"\t\t\\node %@ (%d) at (%@, %@) {%@};\n", + [[n data] tikzList], + i, + formatFloat([n point].x, 4), + formatFloat([n point].y, 4), + [n label] + ]; + i++; + } + + if ([nodes count] > 0) [code appendFormat:@"\t\\end{pgfonlayer}\n"]; + if ([edges count] > 0) [code appendFormat:@"\t\\begin{pgfonlayer}{edgelayer}\n"]; + + NSString *nodeStr; + for (Edge *e in edges) { + [e updateData]; + + if ([e hasEdgeNode]) { + nodeStr = [NSString stringWithFormat:@"node%@{%@} ", + [[[e edgeNode] data] tikzList], + [[e edgeNode] label] + ]; + } else { + nodeStr = @""; + } + + NSString *edata = [[e data] tikzList]; + + NSString *srcAnchor; + NSString *tgtAnchor; + + if ([[e sourceAnchor] isEqual:@""]) { + srcAnchor = @""; + } else { + srcAnchor = [NSString stringWithFormat:@".%@", [e sourceAnchor]]; + } + + if ([[e targetAnchor] isEqual:@""]) { + tgtAnchor = @""; + } else { + tgtAnchor = [NSString stringWithFormat:@".%@", [e targetAnchor]]; + } + + [code appendFormat:@"\t\t\\draw%@ (%@%@) to %@(%@%@);\n", + ([edata isEqual:@""]) ? @"" : [NSString stringWithFormat:@" %@", edata], + [[e source] name], + srcAnchor, + nodeStr, + ([e source] == [e target]) ? @"" : [[e target] name], + tgtAnchor + ]; + } + + if ([edges count] > 0) [code appendFormat:@"\t\\end{pgfonlayer}\n"]; + + [code appendString:@"\\end{tikzpicture}"]; + + [graphLock unlock]; + + return code; +} + ++ (Graph*)graph { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + ++ (Graph*)graphFromTikz:(NSString*)tikz error:(NSError**)e { + return [TikzGraphAssembler parseTikz:tikz error:e]; +} + ++ (Graph*)graphFromTikz:(NSString*)tikz { + return [self graphFromTikz:tikz error:NULL]; +} + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds { + return [self nodeTableForNodes:nds withZone:NSDefaultMallocZone()]; +} + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds withZone:(NSZone*)zone { + NSMapTable *tab = [[NSMapTable allocWithZone:zone] + initWithKeyOptions:NSMapTableStrongMemory + valueOptions:NSMapTableStrongMemory + capacity:[nds count]]; + for (Node *n in nds) { + Node *ncopy = [n copyWithZone:zone]; + [tab setObject:ncopy forKey:n]; +#if ! __has_feature(objc_arc) + [ncopy release]; // tab should still retain ncopy. +#endif + } +#if __has_feature(objc_arc) + return tab; +#else + return [tab autorelease]; +#endif +} + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es { + return [self edgeTableForEdges:es withZone:NSDefaultMallocZone()]; +} + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es withZone:(NSZone*)zone { + NSMapTable *tab = [[NSMapTable allocWithZone:zone] + initWithKeyOptions:NSMapTableStrongMemory + valueOptions:NSMapTableStrongMemory + capacity:[es count]]; + for (Edge *e in es) { + Edge *ecopy = [e copyWithZone:zone]; + [tab setObject:ecopy forKey:e]; +#if ! __has_feature(objc_arc) + [ecopy release]; // tab should still retain ecopy. +#endif + } + return tab; +} + + ++ (NSRect)boundsForNodes:(id<NSFastEnumeration>)nds { + NSPoint tl, br; + NSPoint p; + BOOL hasPoints = NO; + for (Node *n in nds) { + p = [n point]; + if (!hasPoints) { + tl = p; + br = p; + hasPoints = YES; + } else { + if (p.x < tl.x) tl.x = p.x; + if (p.y > tl.y) tl.y = p.y; + if (p.x > br.x) br.x = p.x; + if (p.y < br.y) br.y = p.y; + } + } + + return (hasPoints) ? NSRectAroundPoints(tl, br) : NSMakeRect(0, 0, 0, 0); +} + +@end + +@implementation Graph (Private) +- (void) shapeDictionaryReplaced:(NSNotification*)notification { + for (Edge *e in edges) { + [e recalculateProperties]; + } +} +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphChange.h b/tikzit-1/src/common/GraphChange.h new file mode 100644 index 0000000..0e71a90 --- /dev/null +++ b/tikzit-1/src/common/GraphChange.h @@ -0,0 +1,344 @@ +// +// GraphChange.h +// 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 "Node.h" +#import "Edge.h" + +typedef enum { + GraphAddition, + GraphDeletion, + NodePropertyChange, + EdgePropertyChange, + NodesPropertyChange, + EdgesPropertyChange, + NodesShift, + NodesFlip, + EdgesReverse, + BoundingBoxChange, + GraphPropertyChange, + NodeOrderChange, + EdgeOrderChange +} ChangeType; + +/*! + @class GraphChange + @brief Store the data associated with a graph change. + @details All of the methods that change a graph return an object of type GraphChange. + Graph changes can be re-done by calling [graph applyGraphChange:]. They can be undone + by calling applyGraphChange on [change inverse]. This class has no public initializer, + so everything should be constructed by factory methods. + */ +@interface GraphChange : NSObject { + ChangeType changeType; + + // for addition, deletion, and shifts + NSSet *affectedNodes; + NSSet *affectedEdges; + NSPoint shiftPoint; + + // for flip + BOOL horizontal; + + // for property changes + Node *nodeRef; + Edge *edgeRef; + + Node *oldNode, *nwNode; + Edge *oldEdge, *nwEdge; + NSMapTable *oldNodeTable, *nwNodeTable; + NSMapTable *oldEdgeTable, *nwEdgeTable; + NSRect oldBoundingBox, nwBoundingBox; + GraphElementData *oldGraphData, *nwGraphData; + + NSArray *oldNodeOrder, *nwNodeOrder; + NSArray *oldEdgeOrder, *nwEdgeOrder; +} + +/*! + @property changeType + @brief Type of GraphChange. + */ +@property (assign) ChangeType changeType; + +/*! + @property shiftPoint + @brief A point storing a shifted distance. + */ +@property (assign) NSPoint shiftPoint; + +/*! + @property horizontal + @brief Flags whether nodes were flipped horizontally + */ +@property (assign) BOOL horizontal; + +/*! + @property affectedNodes + @brief A set of nodes affected by this change, may be undefined. + */ +@property (copy) NSSet *affectedNodes; + +/*! + @property affectedEdges + @brief A set of edges affected by this change, may be undefined. + */ +@property (copy) NSSet *affectedEdges; + +/*! + @property nodeRef + @brief A reference to a single node affected by this change, may be undefined. + */ +@property (retain) Node *nodeRef; + +/*! + @property oldNode + @brief A copy of the node pre-change. + */ +@property (copy) Node *oldNode; + +/*! + @property nwNode + @brief A copy of the node post-change. + */ +@property (copy) Node *nwNode; + +/*! + @property edgeRef + @brief A reference to a single edge affected by this change, may be undefined. + */ +@property (retain) Edge *edgeRef; + +/*! + @property oldEdge + @brief A copy of the edge pre-change. + */ +@property (copy) Edge *oldEdge; + +/*! + @property nwEdge + @brief A copy of the edge post-change. + */ +@property (copy) Edge *nwEdge; + +/*! + @property oldNodeTable + @brief A a table containing copies of a set of nodes pre-change. + */ +@property (retain) NSMapTable *oldNodeTable; + +/*! + @property nwNodeTable + @brief A a table containing copies of a set of nodes post-change. + */ +@property (retain) NSMapTable *nwNodeTable; + +/*! + @property oldEdgeTable + @brief A a table containing copies of a set of edges pre-change. + */ +@property (retain) NSMapTable *oldEdgeTable; + +/*! + @property nwEdgeTable + @brief A a table containing copies of a set of edges post-change. + */ +@property (retain) NSMapTable *nwEdgeTable; + +/*! + @property oldBoundingBox + @brief The old bounding box. + */ +@property (assign) NSRect oldBoundingBox; + +/*! + @property nwBoundingBox + @brief The new bounding box. + */ +@property (assign) NSRect nwBoundingBox; + +/*! + @property oldGraphData + @brief The old graph data. + */ +@property (copy) GraphElementData *oldGraphData; + +/*! + @property nwGraphData + @brief The new graph data. + */ +@property (copy) GraphElementData *nwGraphData; + +/*! + @property oldNodeOrder + @brief The old node list. + */ +@property (copy) NSArray *oldNodeOrder; + +/*! + @property nwNodeOrder + @brief The new node list. + */ +@property (copy) NSArray *nwNodeOrder; + +/*! + @property oldEdgeOrder + @brief The old edge list. + */ +@property (copy) NSArray *oldEdgeOrder; + +/*! + @property nwEdgeOrder + @brief The new edge list. + */ +@property (copy) NSArray *nwEdgeOrder; + +/*! + @brief Invert a GraphChange. + @details Invert a GraphChange. Calling [graph applyGraphChange:[[graph msg:...] invert]] + should leave the graph unchanged for any method of Graph that returns a + GraphChange. + @result The inverse of the current Graph Change. + */ +- (GraphChange*)invert; + +/*! + @brief Construct a graph addition. affectedNodes are the added nodes, + affectedEdges are the added edges. + @param ns a set of nodes. + @param es a set of edges. + @result A graph addition. + */ ++ (GraphChange*)graphAdditionWithNodes:(NSSet*)ns edges:(NSSet*)es; + +/*! + @brief Construct a graph deletion. affectedNodes are the deleted nodes, + affectedEdges are the deleted edges. + @param ns a set of nodes. + @param es a set of edges. + @result A graph deletion. + */ ++ (GraphChange*)graphDeletionWithNodes:(NSSet*)ns edges:(NSSet*)es; + +/*! + @brief Construct a property change of a single node. + @param nd the affected node. + @param old a copy of the node pre-change + @param nw a copy of the node post-change + @result A property change of a single node. + */ ++ (GraphChange*)propertyChangeOfNode:(Node*)nd fromOld:(Node*)old toNew:(Node*)nw; + +/*! + @brief Construct a property change of a single edge. + @param e the affected edge. + @param old a copy of the edge pre-change + @param nw a copy of the edge post-change + @result A property change of a single node. + */ ++ (GraphChange*)propertyChangeOfEdge:(Edge*)e fromOld:(Edge *)old toNew:(Edge *)nw; + +/*! + @brief Construct a property change of set of nodes. + @details Construct a property change of set of nodes. oldC and newC should be + constructed using the class method [Graph nodeTableForNodes:] before + and after the property change, respectively. The affected nodes are + keys(oldC) = keys(newC). + @param oldC a table of copies of nodes pre-change + @param newC a table of copies of nodes post-change + @result A property change of a set of nodes. + */ ++ (GraphChange*)propertyChangeOfNodesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC; + +/*! + @brief Construct a property change of set of edges. + @details Construct a property change of set of edges. oldC and newC should be + constructed using the class method [Graph edgeTableForEdges:] before + and after the property change, respectively. The affected edges are + keys(oldC) = keys(newC). + @param oldC a table of copies of edges pre-change + @param newC a table of copies of edges post-change + @result A property change of a set of edges. + */ ++ (GraphChange*)propertyChangeOfEdgesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC; + + +/*! + @brief Construct a shift of a set of nodes by a given point. + @param ns the affected nodes. + @param p a point storing (dx,dy) + @result A shift of a set of nodes. + */ ++ (GraphChange*)shiftNodes:(NSSet*)ns byPoint:(NSPoint)p; + +/*! + @brief Construct a horizontal or vertical flip of a set of nodes. + @param ns the affected nodes. + @param b flag for whether to flip horizontally + @result A flip of a set of nodes. + */ ++ (GraphChange*)flipNodes:(NSSet*)ns horizontal:(BOOL)b; + +/*! + @brief Construct a reversal of a set of edges. + @param es the affected edges. + @result A reverse of a set of edges. + */ ++ (GraphChange*)reverseEdges:(NSSet*)es; + +/*! + @brief Construct a bounding box change + @param oldBB the old bounding box + @param newBB the new bounding box + @result A bounding box change. + */ ++ (GraphChange*)changeBoundingBoxFrom:(NSRect)oldBB to:(NSRect)newBB; + +/*! + @brief Construct a graph property change + @param oldData the old graph data + @param newData the new graph data + @result A graph property change. + */ ++ (GraphChange*)propertyChangeOfGraphFrom:(GraphElementData*)oldData to:(GraphElementData*)newData; + +/*! + @brief Construct a node order change + @param old The old ordering + @param new The new ordering + @result A node order change + */ ++ (GraphChange*)nodeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected; + +/*! + @brief Construct an edge order change + @param old The old ordering + @param new The new ordering + @result A edge order change + */ ++ (GraphChange*)edgeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphChange.m b/tikzit-1/src/common/GraphChange.m new file mode 100644 index 0000000..29a3939 --- /dev/null +++ b/tikzit-1/src/common/GraphChange.m @@ -0,0 +1,369 @@ +// +// GraphChange.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/>. +// + + +// GraphChange : store the data associated to a single, undo-able change +// to a graph. An undo manager should maintain a stack of such changes +// and undo/redo them on request using [graph applyGraphChange:...]. + +#import "GraphChange.h" + + +@implementation GraphChange + +- (id)init { + return [super init]; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [affectedNodes release]; + [affectedEdges release]; + [nodeRef release]; + [edgeRef release]; + [oldNode release]; + [nwNode release]; + [oldEdge release]; + [nwEdge release]; + [oldNodeTable release]; + [nwNodeTable release]; + [oldEdgeTable release]; + [nwEdgeTable release]; + [oldGraphData release]; + [nwGraphData release]; + [oldNodeOrder release]; + [nwNodeOrder release]; + [oldEdgeOrder release]; + [nwEdgeOrder release]; + + [super dealloc]; +#endif +} + +@synthesize changeType; +@synthesize shiftPoint, horizontal; +@synthesize affectedEdges, affectedNodes; +@synthesize edgeRef, nodeRef; +@synthesize nwNode, oldNode; +@synthesize nwEdge, oldEdge; +@synthesize oldNodeTable, nwNodeTable; +@synthesize oldEdgeTable, nwEdgeTable; +@synthesize oldBoundingBox, nwBoundingBox; +@synthesize oldGraphData, nwGraphData; +@synthesize oldNodeOrder, nwNodeOrder; +@synthesize oldEdgeOrder, nwEdgeOrder; + +- (GraphChange*)invert { + GraphChange *inverse = [[GraphChange alloc] init]; + [inverse setChangeType:[self changeType]]; + switch ([self changeType]) { + case GraphAddition: + [inverse setChangeType:GraphDeletion]; +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case GraphDeletion: + [inverse setChangeType:GraphAddition]; +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case NodePropertyChange: +#if __has_feature(objc_arc) + inverse->nodeRef = nodeRef; + inverse->oldNode = nwNode; + inverse->nwNode = oldNode; +#else + inverse->nodeRef = [nodeRef retain]; + inverse->oldNode = [nwNode retain]; + inverse->nwNode = [oldNode retain]; +#endif + break; + case NodesPropertyChange: +#if __has_feature(objc_arc) + +#else + inverse->oldNodeTable = [nwNodeTable retain]; + inverse->nwNodeTable = [oldNodeTable retain]; +#endif + break; + case EdgePropertyChange: +#if __has_feature(objc_arc) + inverse->edgeRef = edgeRef; + inverse->oldEdge = nwEdge; + inverse->nwEdge = oldEdge; +#else + inverse->edgeRef = [edgeRef retain]; + inverse->oldEdge = [nwEdge retain]; + inverse->nwEdge = [oldEdge retain]; +#endif + break; + case EdgesPropertyChange: +#if __has_feature(objc_arc) + inverse->oldEdgeTable = nwEdgeTable; + inverse->nwEdgeTable = oldEdgeTable; +#else + inverse->oldEdgeTable = [nwEdgeTable retain]; + inverse->nwEdgeTable = [oldEdgeTable retain]; +#endif + break; + case NodesShift: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; +#else + inverse->affectedNodes = [affectedNodes retain]; +#endif + [inverse setShiftPoint:NSMakePoint(-[self shiftPoint].x, + -[self shiftPoint].y)]; + break; + case NodesFlip: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; +#else + inverse->affectedNodes = [affectedNodes retain]; +#endif + [inverse setHorizontal:[self horizontal]]; + break; + case EdgesReverse: +#if __has_feature(objc_arc) + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case BoundingBoxChange: + inverse->oldBoundingBox = nwBoundingBox; + inverse->nwBoundingBox = oldBoundingBox; + break; + case GraphPropertyChange: +#if __has_feature(objc_arc) + inverse->oldGraphData = nwGraphData; + inverse->nwGraphData = oldGraphData; +#else + inverse->oldGraphData = [nwGraphData retain]; + inverse->nwGraphData = [oldGraphData retain]; +#endif + break; + case NodeOrderChange: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->oldNodeOrder = nwNodeOrder; + inverse->nwNodeOrder = oldNodeOrder; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->oldNodeOrder = [nwNodeOrder retain]; + inverse->nwNodeOrder = [oldNodeOrder retain]; +#endif + break; + case EdgeOrderChange: +#if __has_feature(objc_arc) + inverse->affectedEdges = affectedEdges; + inverse->oldEdgeOrder = nwEdgeOrder; + inverse->nwEdgeOrder = oldEdgeOrder; +#else + inverse->affectedEdges = [affectedEdges retain]; + inverse->oldEdgeOrder = [nwEdgeOrder retain]; + inverse->nwEdgeOrder = [oldEdgeOrder retain]; +#endif + break; + } +#if __has_feature(objc_arc) + return inverse; +#else + return [inverse autorelease]; +#endif +} + ++ (GraphChange*)graphAdditionWithNodes:(NSSet *)ns edges:(NSSet *)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphAddition]; + [gc setAffectedNodes:ns]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)graphDeletionWithNodes:(NSSet *)ns edges:(NSSet *)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphDeletion]; + [gc setAffectedNodes:ns]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfNode:(Node*)nd fromOld:(Node*)old toNew:(Node*)nw { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodePropertyChange]; + [gc setNodeRef:nd]; + [gc setOldNode:old]; + [gc setNwNode:nw]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfNodesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesPropertyChange]; + [gc setOldNodeTable:oldC]; + [gc setNwNodeTable:newC]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfEdge:(Edge*)e fromOld:(Edge *)old toNew:(Edge *)nw { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgePropertyChange]; + [gc setEdgeRef:e]; + [gc setOldEdge:old]; + [gc setNwEdge:nw]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfEdgesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgesPropertyChange]; + [gc setOldEdgeTable:oldC]; + [gc setNwEdgeTable:newC]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)shiftNodes:(NSSet*)ns byPoint:(NSPoint)p { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesShift]; + [gc setAffectedNodes:ns]; + [gc setShiftPoint:p]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)flipNodes:(NSSet*)ns horizontal:(BOOL)b { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesFlip]; + [gc setAffectedNodes:ns]; + [gc setHorizontal:b]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)reverseEdges:(NSSet*)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgesReverse]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)changeBoundingBoxFrom:(NSRect)oldBB to:(NSRect)newBB { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:BoundingBoxChange]; + [gc setOldBoundingBox:oldBB]; + [gc setNwBoundingBox:newBB]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfGraphFrom:(GraphElementData*)oldData to:(GraphElementData*)newData { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphPropertyChange]; + [gc setOldGraphData:oldData]; + [gc setNwGraphData:newData]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)nodeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodeOrderChange]; + [gc setAffectedNodes:affected]; + [gc setOldNodeOrder:old]; + [gc setNwNodeOrder:new]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)edgeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgeOrderChange]; + [gc setAffectedEdges:affected]; + [gc setOldEdgeOrder:old]; + [gc setNwEdgeOrder:new]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementData.h b/tikzit-1/src/common/GraphElementData.h new file mode 100644 index 0000000..a65e6df --- /dev/null +++ b/tikzit-1/src/common/GraphElementData.h @@ -0,0 +1,94 @@ +// +// GraphElementData.h +// 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 <Foundation/Foundation.h> + +/*! + @class GraphElementData + @brief Store extra data associated with a graph, node, or edge. + @details Store the extra (style, ...) data associated with + a graph, node, or edge. This data is stored as a mutable + array of properties. It also implements hash-like accessors, + but care should be taken using these, as the list can contain + multiple occurrences of the same key. + + Convention: Getters and setters act on the *first* occurrence + of the key. 'Unsetters' remove *all* occurrences. + */ +@interface GraphElementData : NSMutableArray { + NSMutableArray *properties; +} + +- (id)init; ++ (id)data; + +/*! + @brief Set the given value for the *first* property matching this key. Add a + new property if it doesn't already exist. + @param val the value to set + @param key the key for this property + */ +- (void)setProperty:(NSString*)val forKey:(NSString*)key; + +/*! + @brief Remove *all* occurences of the property with the given key. + @param key + */ +- (void)unsetProperty:(NSString*)key; + +/*! + @brief Return the value of the *first* occurrence of the given key. + @param key + */ +- (NSString*)propertyForKey:(NSString*)key; + +/*! + @brief Add the given atom to the list, if it's not already present. + @param atom + */ +- (void)setAtom:(NSString*)atom; + +/*! + @brief Remove *all* occurrences of the given atom. + @param atom + */ +- (void)unsetAtom:(NSString*)atom; + +/*! + @brief Returns YES if the list contains at least one occurrence of + the given atom. + @param atom + @result A boolean value. + */ +- (BOOL)isAtomSet:(NSString*)atom; + +/*! + @brief Returns a TikZ-friendly string containing all of the properties. + @result A string. + */ +- (NSString*)tikzList; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementData.m b/tikzit-1/src/common/GraphElementData.m new file mode 100644 index 0000000..41dc9aa --- /dev/null +++ b/tikzit-1/src/common/GraphElementData.m @@ -0,0 +1,188 @@ +// +// GraphElementData.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 "GraphElementData.h" +#import "GraphElementProperty.h" + + +@implementation GraphElementData + ++ (id)data { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id)init { + self = [super init]; + if (self) { + properties = [[NSMutableArray alloc] init]; + } + return self; +} + +// all of the array messages delegate to 'properties' + +- (NSUInteger)count { return [properties count]; } +- (id)objectAtIndex:(NSUInteger)index { + return [properties objectAtIndex:index]; +} +- (NSArray*)objectsAtIndexes:(NSIndexSet*)indexes { + return [properties objectsAtIndexes:indexes]; +} + +#if __has_feature(objc_arc) +- (void) getObjects:(__unsafe_unretained id*)buffer range:(NSRange)range { +#else +- (void) getObjects:(id*)buffer range:(NSRange)range { +#endif + [properties getObjects:buffer range:range]; +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { + [properties insertObject:anObject atIndex:index]; +} +- (void)removeObjectAtIndex:(NSUInteger)index { + [properties removeObjectAtIndex:index]; +} +- (void)addObject:(id)anObject { + [properties addObject:anObject]; +} +- (void)removeLastObject { + [properties removeLastObject]; +} +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { + [properties replaceObjectAtIndex:index withObject:anObject]; +} + +#if __has_feature(objc_arc) +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unsafe_unretained id [])stackbuf + count:(NSUInteger)len { +#else +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id *)stackbuf + count:(NSUInteger)len { +#endif + return [properties countByEnumeratingWithState:state objects:stackbuf count:len]; +} + +- (void)setProperty:(NSString*)val forKey:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + NSInteger idx = [properties indexOfObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + + GraphElementProperty *p; + if (idx == NSNotFound) { + p = [[GraphElementProperty alloc] initWithPropertyValue:val forKey:key]; + [properties addObject:p]; +#if !__has_feature(objc_arc) + [p release]; +#endif + } else { + p = [properties objectAtIndex:idx]; + [p setValue:val]; + } +} + +- (void)unsetProperty:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + [properties removeObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + +} + +- (NSString*)propertyForKey:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + NSInteger idx = [properties indexOfObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + + + if (idx == NSNotFound) { + return nil; + }else { + GraphElementProperty *p = [properties objectAtIndex:idx]; + return [p value]; + } +} + +- (void)setAtom:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + if (![properties containsObject:a]) [properties addObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif +} + +- (void)unsetAtom:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + [properties removeObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif +} + +- (BOOL)isAtomSet:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + BOOL set = [properties containsObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif + return set; +} + +- (NSString*)tikzList { + NSString *s = [properties componentsJoinedByString:@", "]; + return ([s isEqualToString:@""]) ? @"" : [NSString stringWithFormat:@"[%@]", s]; +} + +- (id)copyWithZone:(NSZone *)zone { + GraphElementData *cp = [[GraphElementData allocWithZone:zone] init]; + for (GraphElementProperty *p in properties) { + GraphElementProperty *p2 = [p copy]; + [cp addObject:p2]; +#if !__has_feature(objc_arc) + [p2 release]; +#endif + } + return cp; +} + +- (void)dealloc { +#if !__has_feature(objc_arc) + [properties release]; + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementProperty.h b/tikzit-1/src/common/GraphElementProperty.h new file mode 100644 index 0000000..057cdbb --- /dev/null +++ b/tikzit-1/src/common/GraphElementProperty.h @@ -0,0 +1,88 @@ +// +// GraphElementProperty.h +// 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 <Foundation/Foundation.h> + +/*! + @class GraphElementProperty + @brief A property. I.e. a single entry in a node's/edge's/graph's + GraphElementData table. + */ +@interface GraphElementProperty : NSObject<NSCopying> { + NSString *key; + NSString *value; + BOOL isAtom; + BOOL isKeyMatch; +} + +@property (copy) NSString *key; +@property (copy) NSString *value; +@property (readonly) BOOL isAtom; +@property (readonly) BOOL isKeyMatch; + +/*! + @brief Initialize a new key-matching object. + @param k a key to match + @result A key-matching object. + */ +- (id)initWithKeyMatching:(NSString*)k; ++ (id)keyMatching:(NSString*)k; + +/*! + @brief Initialize a new atomic property. + @param n the atom's name + @result An atom. + */ +- (id)initWithAtomName:(NSString*)n; ++ (id)atom:(NSString*)n; + +/*! + @brief Initialize a new property. + @param v the property's value + @param k the associated key + @result A property. + */ +- (id)initWithPropertyValue:(NSString*)v forKey:(NSString*)k; ++ (id)property:(NSString*)k withValue:(NSString*)v; + +/*! + @brief A matching function for properties. + @details Two properties match iff their keys match and one of the following: + (a) they are both atomic, (b) one is a key-matching and one is a non-atomic + property, or (c) they are both non-atomic and their values match. + @param object another GraphElementProperty + @result A boolean. + */ +- (BOOL)matches:(GraphElementProperty*)object; + +/*! + @brief An alias for <tt>matches:</tt>. This allows one to use built-in methods that + filter on <tt>isEqual:</tt> for <tt>NSObject</tt>s. + @param object another GraphElementProperty + @result A boolean. + */ +- (BOOL)isEqual:(id)object; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementProperty.m b/tikzit-1/src/common/GraphElementProperty.m new file mode 100644 index 0000000..25e1b15 --- /dev/null +++ b/tikzit-1/src/common/GraphElementProperty.m @@ -0,0 +1,164 @@ +// +// +// GraphElementProperty.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 "GraphElementProperty.h" +#import "NSString+Tikz.h" + +@implementation GraphElementProperty + ++ (id)atom:(NSString*)n { +#if __has_feature(objc_arc) + return [[self alloc] initWithAtomName:n]; +#else + return [[[self alloc] initWithAtomName:n] autorelease]; +#endif +} ++ (id)property:(NSString*)k withValue:(NSString*)v { +#if __has_feature(objc_arc) + return [[self alloc] initWithPropertyValue:v forKey:k]; +#else + return [[[self alloc] initWithPropertyValue:v forKey:k] autorelease]; +#endif +} ++ (id)keyMatching:(NSString*)k { +#if __has_feature(objc_arc) + return [[self alloc] initWithKeyMatching:k]; +#else + return [[[self alloc] initWithKeyMatching:k] autorelease]; +#endif +} + +- (id)initWithAtomName:(NSString*)n { + self = [super init]; + if (self) { + [self setKey:n]; + isAtom = YES; + } + return self; +} + +- (id)initWithPropertyValue:(NSString*)v forKey:(NSString*)k { + self = [super init]; + if (self) { + [self setKey:k]; + [self setValue:v]; + } + return self; +} + +- (id)initWithKeyMatching:(NSString*)k { + self = [super init]; + if (self) { + [self setKey:k]; + isKeyMatch = YES; + } + return self; +} + +- (void) dealloc { +#if ! __has_feature(objc_arc) + [key release]; + [value release]; + [super dealloc]; +#endif +} + +- (void)setValue:(NSString *)v { + if (value != v) { +#if ! __has_feature(objc_arc) + [value release]; +#endif + value = [v copy]; + } +} + +- (NSString*)value { + if (isAtom) { + return @"(atom)"; + } else { + return value; + } +} + + +- (void)setKey:(NSString *)k { + if (key != k) { +#if ! __has_feature(objc_arc) + [key release]; +#endif + key = [k copy]; + } + if (key == nil) + key = @""; // don't allow nil keys +} + +- (NSString*)key { + return key; +} + +- (BOOL)isAtom { return isAtom; } +- (BOOL)isKeyMatch { return isKeyMatch; } + +- (BOOL)matches:(GraphElementProperty*)object { + // properties and atoms are taken to be incomparable + if ([self isAtom] != [object isAtom]) return NO; + + // only compare keys if (a) we are both atoms, (b) i am a key match, or (c) object is a key match + if (([self isAtom] && [object isAtom]) || [self isKeyMatch] || [object isKeyMatch]) { + return [[self key] isEqual:[object key]]; + } + + // otherwise compare key and value + return [[self key] isEqual:[object key]] && [[self value] isEqual:[object value]]; +} + +- (BOOL)isEqual:(id)object { + return [self matches:object]; +} + +- (id)copyWithZone:(NSZone*)zone { + if (isAtom) { + return [[GraphElementProperty allocWithZone:zone] initWithAtomName:[self key]]; + } else if (isKeyMatch) { + return [[GraphElementProperty allocWithZone:zone] initWithKeyMatching:[self key]]; + } else { + return [[GraphElementProperty allocWithZone:zone] initWithPropertyValue:[self value] forKey:[self key]]; + } +} + +- (NSString*)description { + if ([self isAtom]) { + return [[self key] tikzEscapedString]; + } else if ([self isKeyMatch]) { + return [NSString stringWithFormat:@"%@=*", [[self key] tikzEscapedString]]; + } else { + return [NSString stringWithFormat:@"%@=%@", + [[self key] tikzEscapedString], + [[self value] tikzEscapedString]]; + } +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Grid.h b/tikzit-1/src/common/Grid.h new file mode 100644 index 0000000..b267536 --- /dev/null +++ b/tikzit-1/src/common/Grid.h @@ -0,0 +1,110 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "RenderContext.h" +#import "Transformer.h" + +/*! + * Provides a grid, which can be use for snapping points + * + * The grid is divided into cells, and each cell is further subdivided. + * These subdivisions are the snap points for the grid. + */ +@interface Grid: NSObject <NSCopying> { + Transformer *transformer; + float spacing; + int cellSubdivisions; +} + +/*! + * The number of times to subdivide the edge of each cell + * + * Each cell will be divided into cellSubdivisions^2 squares. + */ +@property (assign) int cellSubdivisions; + +/*! + * The cell spacing + * + * Each cell will be @p cellSpacing wide and @p cellSpacing high. + */ +@property (assign) float cellSpacing; + + +/*! + * Create a new grid object. + * + * @param sp the cell spacing - this will be the width and height of each cell + * @param subs the number of cell subdivisions; the cell will end up being + * divided into subs*subs squares that are each sp/subs wide and high + * @param t the transformer to be used when snapping screen points + */ ++ (Grid*) gridWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t; +/*! + * Initialize a grid object. + * + * @param sp the cell spacing - this will be the width and height of each cell + * @param subs the number of cell subdivisions; each cell will end up being + * divided into subs*subs squares that are each sp/subs wide and high + * @param t the transformer to be used when snapping screen points + */ +- (id) initWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t; + +/*! + * Snap a point in screen co-ordinates + * + * @param p the point to snap, in screen co-ordinates + * @result @p p aligned to the nearest corner of a cell subdivision + */ +- (NSPoint) snapScreenPoint:(NSPoint)p; +/*! + * Snap a point in base co-ordinates + * + * @param p the point to snap + * @result @p p aligned to the nearest corner of a cell subdivision + */ +- (NSPoint) snapPoint:(NSPoint)p; + +/** + * Renders the grid + * + * The grid is rendered across the entire surface (subject to the context's + * clip). + * + * The internal transformer is used to convert between graph co-ordinates + * and graphics co-ordinates. + * + * @param cr the context to render in + */ +- (void) renderGridInContext:(id<RenderContext>)cr; + +/** + * Renders the grid + * + * The grid is rendered across the entire surface (subject to the context's + * clip). + * + * @param cr the context to render in + * @param t a transformer that will be used to map graph co-ordinates + * to graphics co-ordinates + */ +- (void) renderGridInContext:(id<RenderContext>)cr transformer:(Transformer*)t; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Grid.m b/tikzit-1/src/common/Grid.m new file mode 100644 index 0000000..f597a4a --- /dev/null +++ b/tikzit-1/src/common/Grid.m @@ -0,0 +1,186 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Grid.h" +#import "util.h" + +@implementation Grid + ++ (Grid*) gridWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t { + return [[[self alloc] initWithSpacing:sp subdivisions:subs transformer:t] autorelease]; +} + +- (id) initWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t { + self = [super init]; + + if (self) { + transformer = [t retain]; + spacing = sp; + cellSubdivisions = subs; + } + + return self; +} + +- (id) copyWithZone:(NSZone*)zone { + return [[Grid allocWithZone:zone] + initWithSpacing:spacing + subdivisions:cellSubdivisions + transformer:transformer]; +} + +- (void) dealloc { + [transformer release]; + [super dealloc]; +} + +- (int) cellSubdivisions { + return cellSubdivisions; +} + +- (void) setCellSubdivisions:(int)count { + cellSubdivisions = count; +} + +- (float) cellSpacing { + return spacing; +} + +- (void) setCellSpacing:(float)sp { + spacing = sp; +} + +- (NSPoint) snapScreenPoint:(NSPoint)point { + NSPoint gridPoint = [transformer fromScreen:point]; + return [transformer toScreen:[self snapPoint:gridPoint]]; +} + +- (NSPoint) snapPoint:(NSPoint)p { + const float snapDistance = spacing/(float)cellSubdivisions; + p.x = roundToNearest (snapDistance, p.x); + p.y = roundToNearest (snapDistance, p.y); + return p; +} + +- (void) _setupLinesForContext:(id<RenderContext>)context withSpacing:(float)offset omittingEvery:(int)omitEvery origin:(NSPoint)origin { + NSRect clip = [context clipBoundingBox]; + float clipx1 = clip.origin.x; + float clipx2 = clipx1 + clip.size.width; + float clipy1 = clip.origin.y; + float clipy2 = clipy1 + clip.size.height; + + // left of the Y axis, moving outwards + unsigned int count = 1; + float x = origin.x - offset; + while (x >= clipx1) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(x, clipy1)]; + [context lineTo:NSMakePoint(x, clipy2)]; + } + + x -= offset; + ++count; + } + // right of the Y axis, moving outwards + count = 1; + x = origin.x + offset; + while (x <= clipx2) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(x, clipy1)]; + [context lineTo:NSMakePoint(x, clipy2)]; + } + + x += offset; + ++count; + } + + // above the Y axis, moving outwards + count = 1; + float y = origin.y - offset; + while (y >= clipy1) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(clipx1, y)]; + [context lineTo:NSMakePoint(clipx2, y)]; + } + + y -= offset; + ++count; + } + // below the Y axis, moving outwards + count = 1; + y = origin.y + offset; + while (y <= clipy2) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(clipx1, y)]; + [context lineTo:NSMakePoint(clipx2, y)]; + } + + y += offset; + ++count; + } +} + +- (void) _renderSubdivisionsWithContext:(id<RenderContext>)context origin:(NSPoint)origin cellSize:(float)cellSize { + const float offset = cellSize / cellSubdivisions; + + [self _setupLinesForContext:context withSpacing:offset omittingEvery:cellSubdivisions origin:origin]; + + [context strokePathWithColor:MakeSolidRColor (0.9, 0.9, 1.0)]; +} + +- (void) _renderCellsWithContext:(id<RenderContext>)context origin:(NSPoint)origin cellSize:(float)cellSize { + [self _setupLinesForContext:context withSpacing:cellSize omittingEvery:0 origin:origin]; + + [context strokePathWithColor:MakeSolidRColor (0.8, 0.8, 0.9)]; +} + +- (void) _renderAxesWithContext:(id<RenderContext>)context origin:(NSPoint)origin { + NSRect clip = [context clipBoundingBox]; + + [context moveTo:NSMakePoint(origin.x, clip.origin.y)]; + [context lineTo:NSMakePoint(origin.x, clip.origin.y + clip.size.height)]; + [context moveTo:NSMakePoint(clip.origin.x, origin.y)]; + [context lineTo:NSMakePoint(clip.origin.x + clip.size.width, origin.y)]; + + [context strokePathWithColor:MakeSolidRColor (0.6, 0.6, 0.7)]; +} + +- (void) renderGridInContext:(id<RenderContext>)cr { + [self renderGridInContext:cr transformer:transformer]; +} + +- (void) renderGridInContext:(id<RenderContext>)context transformer:(Transformer*)t { + const NSPoint origin = [t toScreen:NSZeroPoint]; + const float cellSize = [t scaleToScreen:spacing]; + + [context saveState]; + + // common line settings + [context setLineWidth:1.0]; + [context setAntialiasMode:AntialiasDisabled]; + + [self _renderSubdivisionsWithContext:context origin:origin cellSize:cellSize]; + [self _renderCellsWithContext:context origin:origin cellSize:cellSize]; + [self _renderAxesWithContext:context origin:origin]; + + [context restoreState]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSError+Tikzit.h b/tikzit-1/src/common/NSError+Tikzit.h new file mode 100644 index 0000000..0f45fba --- /dev/null +++ b/tikzit-1/src/common/NSError+Tikzit.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +NSString* const TZErrorDomain; + +enum { + TZ_ERR_OTHER = 1, + TZ_ERR_BADSTATE, + TZ_ERR_BADFORMAT, + TZ_ERR_IO, + TZ_ERR_TOOL_FAILED, + TZ_ERR_NOTDIRECTORY, + TZ_ERR_PARSE +}; + +NSString* const TZToolOutputErrorKey; + +@interface NSError(Tikzit) ++ (NSString*)tikzitErrorDomain; ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code cause:(NSError*)cause; ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code; ++ (id) errorWithLibcError:(NSInteger)errnum; +- (NSString*)toolOutput; +@end + +void logError (NSError *error, NSString *message); + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSError+Tikzit.m b/tikzit-1/src/common/NSError+Tikzit.m new file mode 100644 index 0000000..6b9404b --- /dev/null +++ b/tikzit-1/src/common/NSError+Tikzit.m @@ -0,0 +1,64 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSError+Tikzit.h" + +NSString* const TZErrorDomain = @"tikzit"; + +NSString* const TZToolOutputErrorKey = @"tool-output"; + +@implementation NSError(Tikzit) ++ (NSString*)tikzitErrorDomain { + return TZErrorDomain; +} + ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code cause:(NSError*)cause { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:message forKey:NSLocalizedDescriptionKey]; + if (cause) + [errorDetail setValue:cause forKey:NSUnderlyingErrorKey]; + return [self errorWithDomain:TZErrorDomain code:code userInfo:errorDetail]; +} + ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:TZErrorDomain code:code userInfo:errorDetail]; +} + ++ (id) errorWithLibcError:(NSInteger)errnum { + NSString *message = [NSString stringWithUTF8String:strerror(errnum)]; + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:NSPOSIXErrorDomain code:errnum userInfo:errorDetail]; +} + +- (NSString*)toolOutput { + return [[self userInfo] objectForKey:TZToolOutputErrorKey]; +} + +@end + +void logError (NSError *error, NSString *message) { + if (message == nil) { + NSLog (@"%@", [error localizedDescription]); + } else { + NSLog (@"%@: %@", message, [error localizedDescription]); + } +} + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSFileManager+Utils.h b/tikzit-1/src/common/NSFileManager+Utils.h new file mode 100644 index 0000000..1349919 --- /dev/null +++ b/tikzit-1/src/common/NSFileManager+Utils.h @@ -0,0 +1,29 @@ +// +// NSFileManager+Utils.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// 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 <Foundation/Foundation.h> + +@interface NSFileManager(Utils) +- (BOOL) ensureDirectoryExists:(NSString*)path error:(NSError**)error; +@end + +// vi:ft=objc:sts=4:sw=4:ts=4:noet diff --git a/tikzit-1/src/common/NSFileManager+Utils.m b/tikzit-1/src/common/NSFileManager+Utils.m new file mode 100644 index 0000000..87ede95 --- /dev/null +++ b/tikzit-1/src/common/NSFileManager+Utils.m @@ -0,0 +1,46 @@ +// +// NSFileManager+Utils.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// 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 <Foundation/Foundation.h> +#import "NSError+Tikzit.h" + +@implementation NSFileManager(Utils) +- (BOOL) ensureDirectoryExists:(NSString*)directory error:(NSError**)error { + BOOL isDirectory = NO; + if (![self fileExistsAtPath:directory isDirectory:&isDirectory]) { + if (![self createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:error]) { + return NO; + } + } else if (!isDirectory) { + if (error) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:@"Directory is a file" forKey:NSLocalizedDescriptionKey]; + [errorDetail setValue:directory forKey:NSFilePathErrorKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_NOTDIRECTORY userInfo:errorDetail]; + } + return NO; + } + return YES; +} +@end + +// vi:ft=objc:sts=4:sw=4:ts=4:noet diff --git a/tikzit-1/src/common/NSString+LatexConstants.h b/tikzit-1/src/common/NSString+LatexConstants.h new file mode 100644 index 0000000..f4b5236 --- /dev/null +++ b/tikzit-1/src/common/NSString+LatexConstants.h @@ -0,0 +1,33 @@ +// +// NSString+LatexConstants.h +// 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 <Foundation/Foundation.h> + + +@interface NSString(LatexConstants) + +- (NSString*)stringByExpandingLatexConstants; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+LatexConstants.m b/tikzit-1/src/common/NSString+LatexConstants.m new file mode 100644 index 0000000..634c189 --- /dev/null +++ b/tikzit-1/src/common/NSString+LatexConstants.m @@ -0,0 +1,212 @@ +// +// NSString+LatexConstants.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 "NSString+LatexConstants.h" + +// can't use sizeof() in non-fragile ABI (eg: clang) +#define texConstantCount 63 +static NSString *texConstantNames[texConstantCount] = { + @"alpha", + @"beta", + @"gamma", + @"delta", + @"epsilon", + @"zeta", + @"eta", + @"theta", + @"iota", + @"kappa", + @"lambda", + @"mu", + @"nu", + @"xi", + @"pi", + @"rho", + @"sigma", + @"tau", + @"upsilon", + @"phi", + @"chi", + @"psi", + @"omega", + @"Gamma", + @"Delta", + @"Theta", + @"Lambda", + @"Xi", + @"Pi", + @"Sigma", + @"Upsilon", + @"Phi", + @"Psi", + @"Omega", + + @"pm", + @"to", + @"Rightarrow", + @"Leftrightarrow", + @"forall", + @"partial", + @"exists", + @"emptyset", + @"nabla", + @"in", + @"notin", + @"prod", + @"sum", + @"surd", + @"infty", + @"wedge", + @"vee", + @"cap", + @"cup", + @"int", + @"approx", + @"neq", + @"equiv", + @"leq", + @"geq", + @"subset", + @"supset", + @"cdot", + @"ldots" +}; + +static char * texConstantCodes[texConstantCount] = { + "\u03b1","\u03b2","\u03b3","\u03b4","\u03b5","\u03b6","\u03b7", + "\u03b8","\u03b9","\u03ba","\u03bb","\u03bc","\u03bd","\u03be", + "\u03c0","\u03c1","\u03c3","\u03c4","\u03c5","\u03c6","\u03c7", + "\u03c8","\u03c9","\u0393","\u0394","\u0398","\u039b","\u039e", + "\u03a0","\u03a3","\u03a5","\u03a6","\u03a8","\u03a9", + + "\u00b1","\u2192","\u21d2","\u21d4","\u2200","\u2202","\u2203", + "\u2205","\u2207","\u2208","\u2209","\u220f","\u2211","\u221a", + "\u221e","\u2227","\u2228","\u2229","\u222a","\u222b","\u2248", + "\u2260","\u2261","\u2264","\u2265","\u2282","\u2283","\u22c5", + "\u2026" +}; + +#define texModifierCount 10 +static NSString *texModifierNames[texModifierCount] = { + @"tiny", + @"scriptsize", + @"footnotesize", + @"small", + @"normalsize", + @"large", + @"Large", + @"LARGE", + @"huge", + @"Huge" +}; + +static NSDictionary *texConstants = nil; +static NSSet *texModifiers = nil; + +@implementation NSString(LatexConstants) + +- (NSString*)stringByExpandingLatexConstants { + + if (texConstants == nil) { + NSMutableDictionary *constants = [[NSMutableDictionary alloc] initWithCapacity:texConstantCount]; + for (int i = 0; i < texConstantCount; ++i) { + [constants setObject:[NSString stringWithUTF8String:texConstantCodes[i]] forKey:texConstantNames[i]]; + } + texConstants = constants; + } + if (texModifiers == nil) { + texModifiers = [[NSSet alloc] initWithObjects:texModifierNames count:texModifierCount]; + } + + NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:[self length]]; + NSMutableString *wordBuf = [[NSMutableString alloc] initWithCapacity:10]; + + unichar c_a = [@"a" characterAtIndex:0]; + unichar c_z = [@"z" characterAtIndex:0]; + unichar c_A = [@"A" characterAtIndex:0]; + unichar c_Z = [@"Z" characterAtIndex:0]; + + int state = 0; + // a tiny little DFA to replace \\([\w*]) with unicode of $1 + unichar c; + NSString *code; + int i; + for (i = 0; i<[self length]; ++i) { + c = [self characterAtIndex:i]; + switch (state) { + case 0: + if (c=='\\') { + state = 1; + } else if (c!='$') { + [buf appendFormat:@"%C", c]; + } + break; + case 1: + if ((c>=c_a && c<=c_z) || (c>=c_A && c<=c_Z)) { + [wordBuf appendFormat:@"%C", c]; + } else { + code = [texConstants objectForKey:wordBuf]; + if (code != nil) { + [buf appendString:code]; + } else if (![texModifiers containsObject:wordBuf]) { + [buf appendFormat:@"\\%@", wordBuf]; + } + + [wordBuf setString:@""]; + if (c=='\\') { + state = 1; + } else { + if (c!='$') { + [buf appendFormat:@"%C", c]; + } + state = 0; + } + + } + break; + } + } + + if (state == 1) { + code = [texConstants objectForKey:wordBuf]; + if (code != nil) { + [buf appendString:code]; + } else if (![texModifiers containsObject:wordBuf]) { + [buf appendFormat:@"\\%@", wordBuf]; + } + } + + NSString *ret = [buf copy]; +#if __has_feature(objc_arc) + return ret; +#else + [buf release]; + [wordBuf release]; + + return [ret autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Tikz.h b/tikzit-1/src/common/NSString+Tikz.h new file mode 100644 index 0000000..ea6ea40 --- /dev/null +++ b/tikzit-1/src/common/NSString+Tikz.h @@ -0,0 +1,26 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +@interface NSString (Tikz) + - (NSString*) tikzEscapedString; + - (BOOL) isValidTikzPropertyNameOrValue; + - (BOOL) isValidAnchor; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Tikz.m b/tikzit-1/src/common/NSString+Tikz.m new file mode 100644 index 0000000..1e3073b --- /dev/null +++ b/tikzit-1/src/common/NSString+Tikz.m @@ -0,0 +1,72 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSString+Tikz.h" +#import "TikzGraphAssembler.h" + +@implementation NSString (Tikz) + +- (NSString*) tikzEscapedString { + static NSCharacterSet *avoid = nil; + if (avoid == nil) +#if __has_feature(objc_arc) + avoid = [[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>-'0123456789. "] invertedSet]; +#else + avoid = [[[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>-'0123456789. "] invertedSet] retain]; +#endif + + + if ([self rangeOfCharacterFromSet:avoid].length > 0) { + return [NSString stringWithFormat:@"{%@}", self]; + } else { +#if __has_feature(objc_arc) + return self; +#else + return [[self retain] autorelease]; +#endif + } +} + +- (BOOL) isValidTikzPropertyNameOrValue { + NSUInteger length = [self length]; + unsigned int brace_depth = 0; + unsigned int escape = 0; + for (NSUInteger i = 0; i < length; ++i) { + unichar c = [self characterAtIndex:i]; + + if (escape) { + escape = 0; + } else if (c == '\\') { + escape = 1; + } else if (c == '{') { + brace_depth++; + } else if (c == '}') { + if (brace_depth == 0) + return NO; + brace_depth--; + } + } + return !escape && brace_depth == 0; +} + +- (BOOL) isValidAnchor { + return [TikzGraphAssembler validateTikzEdgeAnchor:self]; +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Util.h b/tikzit-1/src/common/NSString+Util.h new file mode 100644 index 0000000..548edb3 --- /dev/null +++ b/tikzit-1/src/common/NSString+Util.h @@ -0,0 +1,27 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +@interface NSString (Util) + + (NSString*) stringWithContentsOfFile:(NSString*)path + error:(NSError**)error; + - (id) initWithContentsOfFile:(NSString*)path + error:(NSError**)error; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Util.m b/tikzit-1/src/common/NSString+Util.m new file mode 100644 index 0000000..b18f397 --- /dev/null +++ b/tikzit-1/src/common/NSString+Util.m @@ -0,0 +1,66 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "NSString+Util.h" +#import "NSError+Tikzit.h" + +@implementation NSString (Util) ++ (NSString*) stringWithContentsOfFile:(NSString*)path + error:(NSError**)error +{ + return [[[self alloc] initWithContentsOfFile:path error:error] autorelease]; +} +- (id) initWithContentsOfFile:(NSString*)path + error:(NSError**)error +{ + // Fun fact: on GNUstep, at least, + // [stringWithContentsOfFile:usedEncoding:error:] only + // sets error objects if the decoding fails, not if file + // access fails. + // Fun fact 2: on GNUstep, trying to read a directory using + // [stringWithContentsOfFile:] causes an out-of-memory error; + // hence we do these checks *before* trying to read the file. + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + NSString *msg = nil; + if (![fm fileExistsAtPath:path isDirectory:&isDir]) { + msg = [NSString stringWithFormat:@"\"%@\" does not exist", path]; + } else if (isDir) { + msg = [NSString stringWithFormat:@"\"%@\" is a directory", path]; + } else if (![fm isReadableFileAtPath:path]) { + msg = [NSString stringWithFormat:@"\"%@\" is not readable", path]; + } + if (msg != nil) { + if (error) { + *error = [NSError errorWithMessage:msg + code:TZ_ERR_IO]; + } + return nil; + } + self = [self initWithContentsOfFile:path]; + if (self == nil) { + if (error) { + *error = [NSError errorWithMessage:@"unknown error" + code:TZ_ERR_IO]; + } + } + return self; +} +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Node.h b/tikzit-1/src/common/Node.h new file mode 100644 index 0000000..1e580ce --- /dev/null +++ b/tikzit-1/src/common/Node.h @@ -0,0 +1,181 @@ +// +// Node.h +// 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/>. +// + + +// Node : store the data associated with a node. + +#import <Foundation/Foundation.h> + +#import "NodeStyle.h" +#import "GraphElementData.h" + +@class GraphElementProperty; +@class Shape; +@class Transformer; + +/*! + @class Node + @brief A graph node, with associated location and style data. + */ +@interface Node : NSObject<NSCopying> { + NSPoint point; + NodeStyle *style; + NSString *name; + NSString *label; + GraphElementData *data; +} + +/*! + @property shape + @brief The shape to use + @detail This is a convenience property that resolves the shape name + from the style, and uses a circle if there is no style. + + This property is NOT KVO-compliant + */ +@property (readonly) Shape *shape; + +/*! + @property point + @brief The point where this node is located. + */ +@property (assign) NSPoint point; + +/*! + @property style + @brief The style of this node. + */ +@property (retain) NodeStyle *style; + +/*! + @property name + @brief The name of this node. This is a temporary name and may change between + successive TikZ outputs. + */ +@property (copy) NSString *name; + +/*! + @property label + @brief The latex label that appears on this node. + */ +@property (copy) NSString *label; + +/*! + @property data + @brief Associated extra data. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @brief Initialize a new node with the given point. + @param p a point. + @result A node. + */ +- (id)initWithPoint:(NSPoint)p; + +/*! + @brief Initialize a new node at (0,0). + @result A node. + */ +- (id)init; + +/*! + @brief Composes the shape transformer with another transformer + @param t The transform to apply before the shape transform + @result A transformer that first maps according to t, then according + to -shapeTransformer. + */ +- (Transformer*) shapeTransformerFromTransformer:(Transformer*)t; + +/*! + @brief A transformer that may be used to convert the shape to the + right position and scale + */ +- (Transformer*) shapeTransformer; + +/*! + @brief The bounding rect in the given co-ordinate system + @detail This is the bounding rect of the shape (after being + suitably translated and scaled). The label is not + considered. + @param shapeTrans The mapping from graph co-ordinates to the required + co-ordinates + @result The bounding rectangle + */ +- (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans; + +/*! + @brief The bounding rect in graph co-ordinates + @detail This is the bounding rect of the shape (after being suitably + translated and scaled). The label is not considered. + */ +- (NSRect) boundingRect; + +/*! + @brief Try to attach a style of the correct name from the given style list. + @param styles an array of styles. + @result YES if successfully attached, NO otherwise. + */ +- (BOOL)attachStyleFromTable:(NSArray*)styles; + +/*! + @brief Set node properties from <tt>GraphElementData</tt>. + */ +- (void)updateData; + +/*! + @brief Set properties of this node to match the given node. + @param nd a node to mimic. + */ +- (void)setPropertiesFromNode:(Node *)nd; + +/*! + @brief Compare a node to another node using a lex ordering on coordinates. + @param nd another node. + @result A comparison result. + */ +- (NSComparisonResult)compareTo:(id)nd; + +/*! + @brief Factory method to construct a node with the given point. + @param p a point. + @result A node. + */ ++ (Node*)nodeWithPoint:(NSPoint)p; + +/*! + @brief Factory method to construct a node at (0,0). + @result A node. + */ ++ (Node*)node; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Node.m b/tikzit-1/src/common/Node.m new file mode 100644 index 0000000..c5b11d1 --- /dev/null +++ b/tikzit-1/src/common/Node.m @@ -0,0 +1,214 @@ +// +// Node.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 "Node.h" + +#import "Shape.h" + + +@implementation Node + +- (id)initWithPoint:(NSPoint)p { + self = [super init]; + if (self) { + data = [[GraphElementData alloc] init]; + style = nil; + label = @""; + point = p; + } + return self; +} + +- (id)init { + return [self initWithPoint:NSMakePoint(0.0f, 0.0f)]; +} + +- (id)copyWithZone:(NSZone*)z { + Node *cp = [[Node allocWithZone:z] init]; + [cp setPropertiesFromNode:self]; + return cp; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [style release]; + [data release]; + [label release]; + [super dealloc]; +#endif +} + +- (Shape*) shape { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return nil; + } +} + +- (Transformer*) shapeTransformerFromTransformer:(Transformer*)t { + // we take a copy to keep the reflection attributes +#if ! __has_feature(objc_arc) + Transformer *transformer = [[t copy] autorelease]; +#else + Transformer *transformer = [t copy]; +#endif + NSPoint screenPos = [t toScreen:point]; + [transformer setOrigin:screenPos]; + float scale = [t scale]; + if (style) { + scale *= [style scale]; + } + [transformer setScale:scale]; + return transformer; +} + +- (Transformer*) shapeTransformer { + float scale = 1.0f; + if (style) { + scale = [style scale]; + } + return [Transformer transformerWithOrigin:point andScale:scale]; +} + +- (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans { + //if (style) { + return [shapeTrans rectToScreen:[[self shape] boundingRect]]; + /*} else { + NSRect r = NSZeroRect; + r.origin = [shapeTrans toScreen:[self point]]; + return r; + }*/ +} + +- (NSRect) boundingRect { + return [self boundsUsingShapeTransform:[self shapeTransformer]]; +} + +- (BOOL)attachStyleFromTable:(NSArray*)styles { +#if __has_feature(objc_arc) + NSString *style_name = [data propertyForKey:@"style"]; +#else + NSString *style_name = [[[data propertyForKey:@"style"] retain] autorelease]; +#endif + + [self setStyle:nil]; + + // 'none' is a reserved style + if (style_name == nil || [style_name isEqualToString:@"none"]) return YES; + + for (NodeStyle *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 + [self setStyle:[NodeStyle defaultNodeStyleWithName:style_name]]; + return NO; +} + +- (void)updateData { + if (style == nil) { + [data setProperty:@"none" forKey:@"style"]; + } else { + [data setProperty:[style name] forKey:@"style"]; + } +} + +- (void)setPropertiesFromNode:(Node*)nd { + [self setPoint:[nd point]]; + [self setStyle:[nd style]]; + [self setName:[nd name]]; + [self setData:[nd data]]; + [self setLabel:[nd label]]; +} + ++ (Node*)nodeWithPoint:(NSPoint)p { +#if __has_feature(objc_arc) + return [[Node alloc] initWithPoint:p]; +#else + return [[[Node alloc] initWithPoint:p] autorelease]; +#endif +} + ++ (Node*)node { +#if __has_feature(objc_arc) + return [[Node alloc] init]; +#else + return [[[Node alloc] init] autorelease]; +#endif +} + + +// perform a lexicographic ordering (-y, x) on coordinates. +- (NSComparisonResult)compareTo:(id)nd { + Node *node = (Node*)nd; + if (point.y > [node point].y) return NSOrderedAscending; + else if (point.y < [node point].y) return NSOrderedDescending; + else { + if (point.x < [node point].x) return NSOrderedAscending; + else if (point.x > [node point].x) return NSOrderedDescending; + else return NSOrderedSame; + } +} + +@synthesize name; +@synthesize label; +@synthesize point; + +@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]; +} + +- (NodeStyle*)style { + return style; +} + +- (void)setStyle:(NodeStyle *)st { + if (style != st) { +#if __has_feature(objc_arc) + style = st; +#else + NodeStyle *oldStyle = style; + style = [st retain]; + [oldStyle release]; +#endif + } + [self updateData]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NodeStyle.h b/tikzit-1/src/common/NodeStyle.h new file mode 100644 index 0000000..034f95d --- /dev/null +++ b/tikzit-1/src/common/NodeStyle.h @@ -0,0 +1,125 @@ +// +// NodeStyle.h +// 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 "util.h" +#import "ColorRGB.h" +#import "PropertyHolder.h" + +/*! + @class NodeStyle + @brief Store node style information. + @details Store node style information. These properties affect how a node + is displayed in TikZiT. Colors are stored in the ColorRGB struct + to avoid any Cocoa dependency. These styles should be persistant, + which should be implemented in a platform-specific category. For + OS X, this is NodeStyle+Coder. + */ +@interface NodeStyle : PropertyHolder <NSCopying> { + int strokeThickness; + float scale; + ColorRGB *strokeColorRGB; + ColorRGB *fillColorRGB; + NSString *name; + NSString *shapeName; + NSString *category; +} + +/*! + @property strokeThickness + @brief Thickness of the stroke. + */ +@property (assign) int strokeThickness; + +/*! + @property scale + @brief Overall scale of the shape. Defaults to 1.0. + */ +@property (assign) float scale; + + +/*! + @property strokeColorRGB + @brief The stroke color used to render the node + */ +@property (copy) ColorRGB *strokeColorRGB; + +/*! + @property fillColorRGB + @brief The fill color used to render the node + */ +@property (copy) ColorRGB *fillColorRGB; + +/*! + @property name + @brief Style name. + @details Style name. This is the only thing that affects how the node + will look when the latex code is rendered. + */ +@property (copy) NSString *name; + +/*! + @property shapeName + @brief The name of the shape that will be drawn in TikZiT. + */ +@property (copy) NSString *shapeName; + +/*! + @property category + @brief ??? + */ +@property (copy) NSString *category; + +@property (readonly) NSString *tikz; +@property (readonly) BOOL strokeColorIsKnown; +@property (readonly) BOOL fillColorIsKnown; + ++ (int) defaultStrokeThickness; + +/*! + @brief Designated initializer. Construct a blank style with name 'new'. + @result A default style. + */ +- (id)init; + +/*! + @brief Create a named style. + @param nm the style name. + @result A <tt>NodeStyle</tt> with the given name. + */ +- (id)initWithName:(NSString *)nm; + +/*! + @brief Factory method for initWithName: + @param nm the style name. + @result A <tt>NodeStyle</tt> with the given name. + */ ++ (NodeStyle*)defaultNodeStyleWithName:(NSString *)nm; + +/*! + * Make this style the same as the given one + */ +- (void) updateFromStyle:(NodeStyle*)style; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NodeStyle.m b/tikzit-1/src/common/NodeStyle.m new file mode 100644 index 0000000..193d44d --- /dev/null +++ b/tikzit-1/src/common/NodeStyle.m @@ -0,0 +1,246 @@ +// +// NodeStyle.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 "NodeStyle.h" +#import "Shape.h" +#import "ShapeNames.h" + +@implementation NodeStyle + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObjects: + @"fillColorRGB.red", + @"fillColorRGB.blue", + @"fillColorRGB.green", + @"strokeColorRGB.red", + @"strokeColorRGB.blue", + @"strokeColorRGB.green", + @"strokeThickness", + @"shapeName", + @"name", + nil] + triggerChangeNotificationsForDependentKey:@"tikz"]; + [self setKeys:[NSArray arrayWithObjects: + @"fillColorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"fillColorIsKnown"]; + [self setKeys:[NSArray arrayWithObjects: + @"strokeColorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"strokeColorIsKnown"]; +} + ++ (int) defaultStrokeThickness { return 1; } + +- (id)initWithName:(NSString*)nm { + self = [super initWithNotificationName:@"NodeStylePropertyChanged"]; + if (self != nil) { + strokeThickness = [NodeStyle defaultStrokeThickness]; + scale = 1.0f; + strokeColorRGB = [[ColorRGB alloc] initWithRed:0 green:0 blue:0]; + fillColorRGB = [[ColorRGB alloc] initWithRed:255 green:255 blue:255]; + + name = nm; + category = nil; + shapeName = SHAPE_CIRCLE; + } + return self; +} + +- (id)init { + self = [self initWithName:@"new"]; + return self; +} + +- (id)copyWithZone:(NSZone*)zone { + NodeStyle *style = [[NodeStyle allocWithZone:zone] init]; + + [style setStrokeThickness:[self strokeThickness]]; + [style setScale:[self scale]]; + [style setStrokeColorRGB:[self strokeColorRGB]]; + [style setFillColorRGB:[self fillColorRGB]]; + [style setName:[self name]]; + [style setShapeName:[self shapeName]]; + [style setCategory:[self category]]; + + return style; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [category release]; + [shapeName release]; + [strokeColorRGB release]; + [fillColorRGB release]; + [super dealloc]; +#endif +} + +- (NSString*) description { + return [NSString stringWithFormat:@"Node style \"%@\"", name]; +} + +- (void) updateFromStyle:(NodeStyle*)style { + [self setStrokeThickness:[style strokeThickness]]; + [self setScale:[style scale]]; + [self setStrokeColorRGB:[style strokeColorRGB]]; + [self setFillColorRGB:[style fillColorRGB]]; + [self setName:[style name]]; + [self setShapeName:[style shapeName]]; + [self setCategory:[style category]]; +} + ++ (NodeStyle*)defaultNodeStyleWithName:(NSString*)nm { +#if __has_feature(objc_arc) + return [[NodeStyle alloc] initWithName:nm]; +#else + return [[[NodeStyle alloc] initWithName:nm] autorelease]; +#endif +} + +- (NSString*)name { + return name; +} + +- (void)setName:(NSString *)s { + if (name != s) { + NSString *oldValue = name; + name = [s copy]; + [self postPropertyChanged:@"name" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)shapeName { + return shapeName; +} + +- (void)setShapeName:(NSString *)s { + if (shapeName != s) { + NSString *oldValue = shapeName; + shapeName = [s copy]; + [self postPropertyChanged:@"shapeName" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)category { + return category; +} + +- (void)setCategory:(NSString *)s { + if (category != s) { + NSString *oldValue = category; + category = [s copy]; + [self postPropertyChanged:@"category" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (int)strokeThickness { return strokeThickness; } +- (void)setStrokeThickness:(int)i { + int oldValue = strokeThickness; + strokeThickness = i; + [self postPropertyChanged:@"strokeThickness" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (float)scale { return scale; } +- (void)setScale:(float)s { + float oldValue = scale; + scale = s; + [self postPropertyChanged:@"scale" oldValue:[NSNumber numberWithFloat:oldValue]]; +} + +- (ColorRGB*)strokeColorRGB { + return strokeColorRGB; +} + +- (void)setStrokeColorRGB:(ColorRGB*)c { + if (strokeColorRGB != c) { + ColorRGB *oldValue = strokeColorRGB; + strokeColorRGB = [c copy]; + [self postPropertyChanged:@"strokeColorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ColorRGB*)fillColorRGB { + return fillColorRGB; +} + +- (void)setFillColorRGB:(ColorRGB*)c { + if (fillColorRGB != c) { + ColorRGB *oldValue = fillColorRGB; + fillColorRGB = [c copy]; + [self postPropertyChanged:@"fillColorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)tikz { + NSString *fillName = [fillColorRGB name]; + NSString *strokeName = [strokeColorRGB name]; + NSString *stroke = @""; + if (strokeThickness != 1) { + stroke = [NSString stringWithFormat:@",line width=%@ pt", + [NSNumber numberWithFloat:(float)strokeThickness * 0.4f]]; + } + + // If the colors are unknown, fall back on hexnames. These should be defined as colors + // in the Preambles class. + if (fillName == nil) fillName = [fillColorRGB hexName]; + if (strokeName == nil) strokeName = [strokeColorRGB hexName]; + + NSString *shapeDesc = [[Shape shapeForName:shapeName] styleTikz]; + if (shapeDesc == nil) shapeDesc = shapeName; + + return [NSString stringWithFormat:@"\\tikzstyle{%@}=[%@,fill=%@,draw=%@%@]", + name, + shapeDesc, + fillName, + strokeName, + stroke]; +} + +- (BOOL)strokeColorIsKnown { + return ([strokeColorRGB name] != nil); +} + +- (BOOL)fillColorIsKnown { + return ([fillColorRGB name] != nil); +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/PickSupport.h b/tikzit-1/src/common/PickSupport.h new file mode 100644 index 0000000..0749649 --- /dev/null +++ b/tikzit-1/src/common/PickSupport.h @@ -0,0 +1,164 @@ +// +// PickSupport.h +// 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 <Foundation/Foundation.h> +#import "Node.h" +#import "Edge.h" + +/*! + @class PickSupport + @brief Maintain the selection state of nodes and edges. + @detail In addition to the notifications listed for specific methods, + whenever the node selection changes, a "NodeSelectionChanged" + signal is emitted, and whenever the edge selection changes, + an "EdgeSelectionChanged" signal is emitted. + */ +@interface PickSupport : NSObject { + NSMutableSet *selectedNodes; + NSMutableSet *selectedEdges; +} + +/*! + @property selectedNodes + @brief A set of selected nodes. + */ +@property (readonly) NSSet *selectedNodes; + +// KVC methods +- (void)addSelectedNodesObject:(Node*)node; +- (void)addSelectedNodes:(NSSet*)nodes; +- (void)removeSelectedNodesObject:(Node*)node; +- (void)removeSelectedNodes:(NSSet*)nodes; + +/*! + @property selectedEdges + @brief A set of selected edges. + */ +@property (readonly) NSSet *selectedEdges; + +// KVC methods +- (void)addSelectedEdgesObject:(Edge*)edge; +- (void)addSelectedEdges:(NSSet*)edges; +- (void)removeSelectedEdgesObject:(Edge*)edge; +- (void)removeSelectedEdges:(NSSet*)edges; + +/*! + @brief Check if a node is selected. + @param nd a node. + @result YES if nd is selected. + */ +- (BOOL)isNodeSelected:(Node*)nd; + +/*! + @brief Check if an edge is selected. + @param e an edge. + @result YES if e is selected. + */ +- (BOOL)isEdgeSelected:(Edge*)e; + +/*! + @brief Select a node. + @details Sends the "NodeSelected" notification if the node was not + already selected, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)selectNode:(Node*)nd; + +/*! + @brief Deselect a node. + @details Sends the "NodeDeselected" notification if the node was + selected, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)deselectNode:(Node*)nd; + +/*! + @brief Select an edge. + @details Sends the "EdgeSelected" notification if the node was not + already selected, with @p e as "edge" in the userInfo + @param e an edge. + */ +- (void)selectEdge:(Edge*)e; + +/*! + @brief Deselect an edge. + @details Sends the "EdgeDeselected" notification if the node was + selected, with @p e as "edge" in the userInfo + @param e an edge. + */ +- (void)deselectEdge:(Edge*)e; + +/*! + @brief Toggle the selected state of the given node. + @details Sends the "NodeSelected" or "NodeDeselected" notification as + appropriate, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)toggleNodeSelected:(Node*)nd; + +/*! + @brief Select all nodes in the given set. + @details Sends the "NodeSelectionReplaced" notification if this + caused the selection to change. + + Equivalent to selectAllNodes:nodes replacingSelection:YES + @param nodes a set of nodes. + */ +- (void)selectAllNodes:(NSSet*)nodes; + +/*! + @brief Select all nodes in the given set. + @details Sends the "NodeSelectionReplaced" notification if this + caused the selection to change. + + If replace is NO, @p nodes will be added to the existing + selection, otherwise it will replace the existing selection. + @param nodes a set of nodes. + @param replace whether to replace the existing selection + */ +- (void)selectAllNodes:(NSSet*)nodes replacingSelection:(BOOL)replace; + +/*! + @brief Deselect all nodes. + @details Sends the "NodeSelectionReplaced" notification if there + were any nodes previously selected + */ +- (void)deselectAllNodes; + +/*! + @brief Deselect all edges. + @details Sends the "EdgeSelectionReplaced" notification if there + were any edges previously selected + */ +- (void)deselectAllEdges; + +/*! + @brief Factory method for getting a new <tt>PickSupport</tt> object. + @result An empty <tt>PickSupport</tt>. + */ ++ (PickSupport*)pickSupport; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/PickSupport.m b/tikzit-1/src/common/PickSupport.m new file mode 100644 index 0000000..560fc2c --- /dev/null +++ b/tikzit-1/src/common/PickSupport.m @@ -0,0 +1,232 @@ +// +// PickSupport.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 "PickSupport.h" + + +@implementation PickSupport + +- (void) postNodeSelectionChanged { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionChanged" + object:self]; +} + +- (void) postEdgeSelectionChanged { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionChanged" + object:self]; +} + +- (id) init { + self = [super init]; + + if (self) { +#if __has_feature(objc_arc) + selectedNodes = [NSMutableSet set]; + selectedEdges = [NSMutableSet set]; +#else + selectedNodes = [[NSMutableSet set] retain]; + selectedEdges = [[NSMutableSet set] retain]; +#endif + } + + return self; +} + ++ (PickSupport*)pickSupport { +#if __has_feature(objc_arc) + return [[PickSupport alloc] init]; +#else + return [[[PickSupport alloc] init] autorelease]; +#endif +} + +@synthesize selectedNodes; +- (void)addSelectedNodesObject:(Node*)node { + return [self selectNode:node]; +} +- (void)addSelectedNodes:(NSSet*)nodes { + return [self selectAllNodes:nodes replacingSelection:NO]; +} +- (void)removeSelectedNodesObject:(Node*)node { + return [self deselectNode:node]; +} +- (void)removeSelectedNodes:(NSSet*)nodes { + if ([selectedNodes count] > 0) { + [selectedNodes minusSet:nodes]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; + } +} + +@synthesize selectedEdges; +- (void)addSelectedEdgesObject:(Edge*)edge { + return [self selectEdge:edge]; +} +- (void)addSelectedEdges:(NSSet*)edges { + if (selectedEdges == edges) { + return; + } + if ([edges count] == 0) { + return; + } + + [selectedEdges unionSet:edges]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; +} +- (void)removeSelectedEdgesObject:(Edge*)edge { + return [self deselectEdge:edge]; +} +- (void)removeSelectedEdges:(NSSet*)edges { + if ([selectedEdges count] > 0 && [edges count] > 0) { + [selectedEdges minusSet:edges]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; + } +} + +- (BOOL)isNodeSelected:(Node*)nd { + return [selectedNodes containsObject:nd]; +} + +- (BOOL)isEdgeSelected:(Edge*)e { + return [selectedEdges containsObject:e]; +} + +- (void)selectNode:(Node*)nd { + if (nd != nil && ![selectedNodes member:nd]) { + [selectedNodes addObject:nd]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelected" + object:self + userInfo:[NSDictionary dictionaryWithObject:nd forKey:@"node"]]; + [self postNodeSelectionChanged]; + } +} + +- (void)deselectNode:(Node*)nd { + if (nd != nil && [selectedNodes member:nd]) { + [selectedNodes removeObject:nd]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeDeselected" + object:self + userInfo:[NSDictionary dictionaryWithObject:nd forKey:@"node"]]; + [self postNodeSelectionChanged]; + } +} + +- (void)selectEdge:(Edge*)e { + if (e != nil && ![selectedEdges member:e]) { + [selectedEdges addObject:e]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelected" + object:self + userInfo:[NSDictionary dictionaryWithObject:e forKey:@"edge"]]; + [self postEdgeSelectionChanged]; + } +} + +- (void)deselectEdge:(Edge*)e { + if (e != nil && [selectedEdges member:e]) { + [selectedEdges removeObject:e]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeDeselected" + object:self + userInfo:[NSDictionary dictionaryWithObject:e forKey:@"edge"]]; + [self postEdgeSelectionChanged]; + } +} + +- (void)toggleNodeSelected:(Node*)nd { + if ([self isNodeSelected:nd]) + [self deselectNode:nd]; + else + [self selectNode:nd]; +} + +- (void)selectAllNodes:(NSSet*)nodes { + [self selectAllNodes:nodes replacingSelection:YES]; +} + +- (void)selectAllNodes:(NSSet*)nodes replacingSelection:(BOOL)replace { + if (selectedNodes == nodes) { + return; + } + if (!replace && [nodes count] == 0) { + return; + } + + if (replace) { +#if ! __has_feature(objc_arc) + [selectedNodes release]; +#endif + selectedNodes = [nodes mutableCopy]; + } else { + [selectedNodes unionSet:nodes]; + } + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; +} + +- (void)deselectAllNodes { + if ([selectedNodes count] > 0) { + [selectedNodes removeAllObjects]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; + } +} + +- (void)deselectAllEdges { + if ([selectedEdges count] > 0) { + [selectedEdges removeAllObjects]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; + } +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [selectedNodes release]; + [selectedEdges release]; + + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Preambles.h b/tikzit-1/src/common/Preambles.h new file mode 100644 index 0000000..2fb084a --- /dev/null +++ b/tikzit-1/src/common/Preambles.h @@ -0,0 +1,73 @@ +// +// Preambles.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// Copyright 2011 Alex Merry. 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 <Foundation/Foundation.h> +#import "StyleManager.h" + +@class Graph; + +@interface Preambles : NSObject { + NSMutableDictionary *preambleDict; + NSString *selectedPreambleName; + NSArray *styles; + NSArray *edges; + StyleManager *styleManager; +} + +@property (copy) NSString *selectedPreambleName; +@property (retain) NSString *currentPreamble; +@property (retain) StyleManager *styleManager; +@property (readonly) NSMutableDictionary *preambleDict; + ++ (Preambles*)preambles; +- (id)init; +- (void)setStyles:(NSArray*)sty; +- (void)setEdges:(NSArray*)edg; + +- (NSString*)preambleForName:(NSString*)name; +- (BOOL)setPreamble:(NSString*)content forName:(NSString*)name; + +- (NSString*)addPreamble; +- (NSString*)addPreambleWithNameBase:(NSString*)name; + +- (BOOL)renamePreambleFrom:(NSString*)old to:(NSString*)new; +- (BOOL)removePreamble:(NSString*)name; + +- (NSEnumerator*)customPreambleNameEnumerator; + +- (void)removeAllPreambles; + +- (BOOL)selectedPreambleIsDefault; + +- (NSString*)styleDefinitions; +- (NSString*)defaultPreamble; +- (NSString*)defaultPreambleName; +- (NSString*)currentPostamble; + +- (NSString*)buildDocumentForTikz:(NSString*)tikz; +- (NSString*)buildDocumentForGraph:(Graph*)g; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Preambles.m b/tikzit-1/src/common/Preambles.m new file mode 100644 index 0000000..922fc30 --- /dev/null +++ b/tikzit-1/src/common/Preambles.m @@ -0,0 +1,320 @@ +// +// Preambles.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 "Preambles.h" +#import "NodeStyle.h" +#import "EdgeStyle.h" +#import "Graph.h" + +static NSString *DEF_PREAMBLE_START = +@"\\usepackage[svgnames]{xcolor}\n" +@"\\usepackage{tikz}\n" +@"\\usetikzlibrary{decorations.markings}\n" +@"\\usetikzlibrary{shapes.geometric}\n" +@"\n" +@"\\pgfdeclarelayer{edgelayer}\n" +@"\\pgfdeclarelayer{nodelayer}\n" +@"\\pgfsetlayers{edgelayer,nodelayer,main}\n" +@"\n" +@"\\tikzstyle{none}=[inner sep=0pt]\n"; + +static NSString *PREAMBLE_TAIL = +@"\n" +@"\\pagestyle{empty}\n" +@"\\usepackage[graphics,tightpage,active]{preview}\n" +@"\\PreviewEnvironment{tikzpicture}\n" +@"\\newlength{\\imagewidth}\n" +@"\\newlength{\\imagescale}\n" +@"\n" +@"\\begin{document}\n"; + +static NSString *POSTAMBLE = +@"\n" +@"\\end{document}\n"; + +@implementation Preambles + ++ (Preambles*)preambles { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id)init { + self = [super init]; + if (self) { + selectedPreambleName = @"default"; + preambleDict = [[NSMutableDictionary alloc] initWithCapacity:1]; + [preambleDict setObject:[self defaultPreamble] forKey:@"custom"]; + styles = nil; + edges = nil; + styleManager = nil; + } + return self; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [selectedPreambleName release]; + [styles release]; + [styleManager release]; + [super dealloc]; +#endif +} + +- (NSString*)preambleForName:(NSString*)name { + if ([name isEqualToString:@"default"]) + return [self defaultPreamble]; + else + return [preambleDict objectForKey:name]; +} + +- (BOOL)setPreamble:(NSString*)content forName:(NSString*)name { + if ([name isEqualToString:@"default"]) + return NO; + [preambleDict setObject:content forKey:name]; + return YES; +} + +- (void)removeAllPreambles { + [preambleDict removeAllObjects]; +} + +- (NSEnumerator*)customPreambleNameEnumerator { + return [preambleDict keyEnumerator]; +} + +- (void)setStyles:(NSArray*)sty { +#if ! __has_feature(objc_arc) + [sty retain]; + [styles release]; +#endif + styles = sty; +} + +- (void)setEdges:(NSArray*)edg { +#if ! __has_feature(objc_arc) + [edg retain]; + [edges release]; +#endif + edges = edg; +} + +- (NSString*)styleDefinitions { + if (styleManager != nil) { + [self setStyles:[styleManager nodeStyles]]; + } +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + NSMutableString *buf = [NSMutableString string]; + NSMutableString *colbuf = [NSMutableString string]; + NSMutableSet *colors = [NSMutableSet setWithCapacity:2*[styles count]]; + for (NodeStyle *st in styles) { + [buf appendFormat:@"%@\n", [st tikz]]; + ColorRGB *fill = [st fillColorRGB]; + ColorRGB *stroke = [st strokeColorRGB]; + if ([fill name] == nil && ![colors containsObject:fill]) { + [colors addObject:fill]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [fill hexName], [fill redFloat], [fill greenFloat], [fill blueFloat]]; + } + + if ([stroke name] == nil && ![colors containsObject:stroke]) { + [colors addObject:stroke]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [stroke hexName], [stroke redFloat], [stroke greenFloat], [stroke blueFloat]]; + } + } + + if (styleManager != nil) { + [self setEdges:[styleManager edgeStyles]]; + } + + [buf appendString:@"\n"]; + for (EdgeStyle *st in edges) { + [buf appendFormat:@"%@\n", [st tikz]]; + ColorRGB *color = [st colorRGB]; + if (color != nil && [color name] == nil && ![colors containsObject:color]) { + [colors addObject:color]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [color hexName], [color redFloat], [color greenFloat], [color blueFloat]]; + } + } + + NSString *defs = [[NSString alloc] initWithFormat:@"%@\n%@", colbuf, buf]; + +#if __has_feature(objc_arc) + return defs; +#else + [pool drain]; + return [defs autorelease]; +#endif +} + +- (NSString*)defaultPreamble { + return [NSString stringWithFormat:@"%@%@", + DEF_PREAMBLE_START, [self styleDefinitions]]; +} + +- (BOOL)selectedPreambleIsDefault { + return [selectedPreambleName isEqualToString:@"default"]; +} + +- (NSString*)selectedPreambleName { return selectedPreambleName; } +- (void)setSelectedPreambleName:(NSString *)sel { + if (sel != selectedPreambleName) { +#if ! __has_feature(objc_arc) + [selectedPreambleName release]; +#endif + selectedPreambleName = [sel copy]; + } +} + +- (NSString*)currentPreamble { + NSString *pre = [self preambleForName:selectedPreambleName]; + return (pre == nil) ? [self defaultPreamble] : pre; +} + +- (void)setCurrentPreamble:(NSString*)str { + if (![selectedPreambleName isEqualToString:@"default"]) + [preambleDict setObject:str forKey:selectedPreambleName]; +} + +- (StyleManager*)styleManager { + return styleManager; +} + +- (void)setStyleManager:(StyleManager *)manager { +#if ! __has_feature(objc_arc) + [manager retain]; + [styleManager release]; +#endif + styleManager = manager; +} + +- (NSString*)currentPostamble { + return POSTAMBLE; +} + +- (NSMutableDictionary*)preambleDict { + return preambleDict; +} + +- (NSString*)defaultPreambleName { + return @"default"; +} + +- (NSString*)addPreamble { + return [self addPreambleWithNameBase:@"new preamble"]; +} + +- (NSString*)addPreambleWithNameBase:(NSString*)base { + if ([preambleDict objectForKey:base] == nil) { + [self setPreamble:[self defaultPreamble] forName:base]; + return base; + } + int i = 0; + NSString *tryName = nil; + do { + ++i; + tryName = [NSString stringWithFormat:@"%@ %d", base, i]; + } while ([preambleDict objectForKey:tryName] != nil); + + [self setPreamble:[self defaultPreamble] forName:tryName]; + return tryName; +} + +- (BOOL)renamePreambleFrom:(NSString*)old to:(NSString*)new { + if ([old isEqualToString:@"default"]) + return NO; + if ([new isEqualToString:@"default"]) + return NO; + if ([old isEqualToString:new]) + return YES; + BOOL isSelected = NO; + if ([old isEqualToString:selectedPreambleName]) { + [self setSelectedPreambleName:nil]; + isSelected = YES; + } + NSString *preamble = [preambleDict objectForKey:old]; +#if ! __has_feature(objc_arc) + [preamble retain]; +#endif + [preambleDict removeObjectForKey:old]; + [preambleDict setObject:preamble forKey:new]; +#if ! __has_feature(objc_arc) + [preamble release]; +#endif + if (isSelected) { + [self setSelectedPreambleName:new]; + } + return YES; +} + +- (BOOL)removePreamble:(NSString*)name { + if ([name isEqualToString:@"default"]) + return NO; + // "name" may be held only by being the selected preamble... +#if ! __has_feature(objc_arc) + [name retain]; +#endif + if ([name isEqualToString:selectedPreambleName]) + [self setSelectedPreambleName:nil]; + [preambleDict removeObjectForKey:name]; +#if ! __has_feature(objc_arc) + [name release]; +#endif + return YES; +} + +- (NSString*)buildDocumentForTikz:(NSString*)tikz +{ + NSString *preamble = [self currentPreamble]; + NSString *doc_head = @""; + if (![preamble hasPrefix:@"\\documentclass"]) { + doc_head = @"\\documentclass{article}\n"; + } + NSString *preamble_suffix = @""; + if ([preamble rangeOfString:@"\\begin{document}" + options:NSBackwardsSearch].length == 0) { + preamble_suffix = PREAMBLE_TAIL; + } + return [NSString stringWithFormat:@"%@%@%@%@%@", + doc_head, + [self currentPreamble], + preamble_suffix, + tikz, + POSTAMBLE]; +} + +- (NSString*)buildDocumentForGraph:(Graph*)g +{ + return [self buildDocumentForTikz:[g tikz]]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/PropertyHolder.h b/tikzit-1/src/common/PropertyHolder.h new file mode 100644 index 0000000..ba1d825 --- /dev/null +++ b/tikzit-1/src/common/PropertyHolder.h @@ -0,0 +1,36 @@ +// +// PropertyHolder.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> + +@interface PropertyHolder : NSObject { + NSString *notificationName; +} + +- (id)initWithNotificationName:(NSString*)name; +- (void) postPropertyChanged:(NSString*)property oldValue:(id)value; +- (void) postPropertyChanged:(NSString*)property; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/PropertyHolder.m b/tikzit-1/src/common/PropertyHolder.m new file mode 100644 index 0000000..6aaf125 --- /dev/null +++ b/tikzit-1/src/common/PropertyHolder.m @@ -0,0 +1,74 @@ +// +// PropertyHolder.m +// TikZiT +// +// Copyright 2011 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 "PropertyHolder.h" + +@implementation PropertyHolder + + +- (id)init { + self = [super init]; + if (self) { + notificationName = @"UnknownPropertyChanged"; + } + return self; +} + +- (id)initWithNotificationName:(NSString*)n { + self = [super init]; + if (self) { + notificationName = [n copy]; + } + return self; +} + +- (void)postPropertyChanged:(NSString*)property oldValue:(id)value { + NSDictionary *userInfo; + if (value != nil) { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + property, @"propertyName", + value, @"oldValue", + nil]; + } else { + userInfo = [NSDictionary dictionaryWithObject:property + forKey:@"propertyName"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:notificationName + object:self + userInfo:userInfo]; +} + +- (void)postPropertyChanged:(NSString*)property { + [self postPropertyChanged:property oldValue:nil]; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [notificationName release]; + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:et:sts=4:sw=4 diff --git a/tikzit-1/src/common/RColor.h b/tikzit-1/src/common/RColor.h new file mode 100644 index 0000000..7f22547 --- /dev/null +++ b/tikzit-1/src/common/RColor.h @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +#ifndef CGFloat +#define CGFloat float +#endif + +/** + * A lightweight color structure used by RenderContext + * + * This is mainly to avoid the overhead of ColorRGB when + * rendering things not based on a NodeStyle + * + * All values range from 0.0f to 1.0f. + */ +typedef struct { + CGFloat red; + CGFloat green; + CGFloat blue; + CGFloat alpha; +} +RColor; + +/** Solid white */ +static const RColor WhiteRColor __attribute__((unused)) = {1.0, 1.0, 1.0, 1.0}; +/** Solid black */ +static const RColor BlackRColor __attribute__((unused)) = {0.0, 0.0, 0.0, 1.0}; + +/** Create a color with alpha set to 1.0 */ +RColor MakeSolidRColor (CGFloat red, CGFloat green, CGFloat blue); +/** Create a color */ +RColor MakeRColor (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RColor.m b/tikzit-1/src/common/RColor.m new file mode 100644 index 0000000..49914fe --- /dev/null +++ b/tikzit-1/src/common/RColor.m @@ -0,0 +1,33 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "RColor.h" + +RColor MakeSolidRColor (CGFloat red, CGFloat green, CGFloat blue) { + return MakeRColor (red, green, blue, 1.0); +} + +RColor MakeRColor (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + RColor color; + color.red = red; + color.green = green; + color.blue = blue; + color.alpha = alpha; + return color; +} + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RectangleShape.h b/tikzit-1/src/common/RectangleShape.h new file mode 100644 index 0000000..3fa0f31 --- /dev/null +++ b/tikzit-1/src/common/RectangleShape.h @@ -0,0 +1,33 @@ +// +// RectangleShape.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "Shape.h" + + +@interface RectangleShape : Shape { +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RectangleShape.m b/tikzit-1/src/common/RectangleShape.m new file mode 100644 index 0000000..db9c803 --- /dev/null +++ b/tikzit-1/src/common/RectangleShape.m @@ -0,0 +1,57 @@ +// +// RectangleShape.m +// TikZiT +// +// Copyright 2011 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 "RectangleShape.h" +#import "Node.h" +#import "Edge.h" + +@implementation RectangleShape + +- (id)init { + self = [super init]; + if (self) { + Node *n0,*n1,*n2,*n3; + float sz = 0.2f; + + n0 = [Node nodeWithPoint:NSMakePoint(-sz, sz)]; + n1 = [Node nodeWithPoint:NSMakePoint( sz, sz)]; + n2 = [Node nodeWithPoint:NSMakePoint( sz,-sz)]; + n3 = [Node nodeWithPoint:NSMakePoint(-sz,-sz)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"rectangle"; + } + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RegularPolyShape.h b/tikzit-1/src/common/RegularPolyShape.h new file mode 100644 index 0000000..1fd8f1e --- /dev/null +++ b/tikzit-1/src/common/RegularPolyShape.h @@ -0,0 +1,50 @@ +// +// RegularPolyShape.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "Shape.h" + +/** + * A regular polygon + * + * Matches the "regular polygon" shape in the shapes.geometric + * PGF/TikZ library. + */ +@interface RegularPolyShape : Shape { +} + +/** + * Initialise a regular polygon + * + * A rotation of 0 will produce a polygon with one + * edge flat along the bottom (just like PGF/TikZ + * does it). + * + * @param sides the number of sides the polygon should have + * @param rotation the rotation of the polygon, in degrees + */ +- (id)initWithSides:(int)sides rotation:(int)rotation; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RegularPolyShape.m b/tikzit-1/src/common/RegularPolyShape.m new file mode 100644 index 0000000..3555115 --- /dev/null +++ b/tikzit-1/src/common/RegularPolyShape.m @@ -0,0 +1,76 @@ +// +// RegularPolyShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger +// Copyright 2012 Alex Merry +// 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 "RegularPolyShape.h" +#import "Node.h" +#import "Edge.h" +#import "util.h" + +@implementation RegularPolyShape + +- (id)initWithSides:(int)sides rotation:(int)rotation { + self = [super init]; + if (self == nil) + return nil; + + // TikZ draws regular polygons using a radius inscribed + // _inside_ the shape (touching middles of edges), not + // outside (touching points) + const float innerRadius = 0.2f; + + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:sides]; + NSMutableArray *edges = [NSMutableArray arrayWithCapacity:sides]; + + float dtheta = (M_PI * 2.0f) / ((float)sides); + float theta = (dtheta/2.0f) - (M_PI / 2.0f); + theta += degreesToRadians(rotation); + // radius of the outer circle + float radius = ABS(innerRadius / cos(dtheta)); + + for (int i = 0; i < sides; ++i) { + NSPoint p; + p.x = radius * cos(theta); + p.y = radius * sin(theta); + + [nodes addObject:[Node nodeWithPoint:p]]; + theta += dtheta; + } + + for (int i = 0; i < sides; ++i) { + [edges addObject:[Edge edgeWithSource:[nodes objectAtIndex:i] + andTarget:[nodes objectAtIndex:(i+1)%sides]]]; + } + + paths = [[NSSet alloc] initWithObjects:edges,nil]; + + styleTikz = [[NSString alloc] initWithFormat: + @"regular polygon,regular polygon sides=%d,shape border rotate=%d", + sides, rotation]; + + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RenderContext.h b/tikzit-1/src/common/RenderContext.h new file mode 100644 index 0000000..8633944 --- /dev/null +++ b/tikzit-1/src/common/RenderContext.h @@ -0,0 +1,156 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "RColor.h" + +typedef enum { + AntialiasDisabled, + AntialiasDefault +} AntialiasMode; + +// encapsulates a CTLine on OSX and +// a PangoLayout in GTK+ +@protocol TextLayout +@property (readonly) NSSize size; +@property (readonly) NSString *text; +- (void) showTextAt:(NSPoint)topLeft withColor:(RColor)color; +@end + +@protocol RenderContext +- (void) saveState; +- (void) restoreState; + +- (NSRect) clipBoundingBox; +- (BOOL) strokeIncludesPoint:(NSPoint)p; +- (BOOL) fillIncludesPoint:(NSPoint)p; +- (id<TextLayout>) layoutText:(NSString*)text withSize:(CGFloat)fontSize; + +// this may not affect text rendering +- (void) setAntialiasMode:(AntialiasMode)mode; +- (void) setLineWidth:(CGFloat)width; +// setting to 0 will unset the dash +- (void) setLineDash:(CGFloat)dashLength; + +/** + * Clear the current path, including all subpaths + */ +- (void) startPath; +/** + * Close the current subpath + */ +- (void) closeSubPath; +/** + * Start a new subpath, and set the current point. + * + * The point will be the current point and the starting point + * for the subpath. + */ +- (void) moveTo:(NSPoint)p; +/** + * Add a cubic bezier curve to the current subpath. + * + * The curve will start at the current point, terminate at end and + * be defined by cp1 and cp2. + */ +- (void) curveTo:(NSPoint)end withCp1:(NSPoint)cp1 andCp2:(NSPoint)cp2; +/** + * Add a straight line to the current subpath. + * + * The line will start at the current point, and terminate at end. + */ +- (void) lineTo:(NSPoint)end; +/** + * Add a new rectangular subpath. + * + * The current point is undefined after this call. + */ +- (void) rect:(NSRect)rect; +/** + * Add a new circular subpath. + * + * The current point is undefined after this call. + */ +- (void) circleAt:(NSPoint)c withRadius:(CGFloat)r; + +/** + * Paint along the current path. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) strokePathWithColor:(RColor)color; +/** + * Paint inside the current path. + * + * The fill colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) fillPathWithColor:(RColor)color; +/** + * Paint along and inside the current path. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + * + * Note that the fill and stroke may overlap, although + * the stroke is always painted on top, so this is only + * relevant when the stroke colour has an alpha channel + * other than 1.0f. + */ +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor; +/** + * Paint along and inside the current path using an alpha channel. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + * + * Note that the fill and stroke may overlap, although + * the stroke is always painted on top, so this is only + * relevant when the stroke colour has an alpha channel + * other than 1.0f. + */ +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor + usingAlpha:(CGFloat)alpha; +/** + * Set the clip to the current path. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) clipToPath; + +/** + * Paint everywhere within the clip. + */ +- (void) paintWithColor:(RColor)color; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Shape.h b/tikzit-1/src/common/Shape.h new file mode 100644 index 0000000..b401a87 --- /dev/null +++ b/tikzit-1/src/common/Shape.h @@ -0,0 +1,49 @@ +// +// Shape.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "Transformer.h" + +@interface Shape : NSObject <NSCopying> { + NSSet *paths; + NSRect boundingRect; // cache + NSString *styleTikz; +} + +@property (retain) NSSet *paths; +@property (readonly) NSRect boundingRect; +/** + * The tikz code to use in style properties for this shape + * + * This can return nil, in which case the shape name should be used + */ +@property (retain) NSString *styleTikz; + +- (id)init; ++ (void)refreshShapeDictionary; ++ (NSDictionary*)shapeDictionary; ++ (Shape*)shapeForName:(NSString*)shapeName; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Shape.m b/tikzit-1/src/common/Shape.m new file mode 100644 index 0000000..e887688 --- /dev/null +++ b/tikzit-1/src/common/Shape.m @@ -0,0 +1,171 @@ +// +// Shape.m +// TikZiT +// +// Copyright 2011 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 "Shape.h" + +#import "Edge.h" +#import "SupportDir.h" +#import "ShapeNames.h" + +#import "CircleShape.h" +#import "DiamondShape.h" +#import "RectangleShape.h" +#import "RegularPolyShape.h" +#import "TikzShape.h" + +#import "util.h" + +@implementation Shape + +- (void)calcBoundingRect { + boundingRect = NSZeroRect; + + if (paths == nil) + return; + + for (NSArray *arr in paths) { + for (Edge *e in arr) { + boundingRect = NSUnionRect(boundingRect, [e boundingRect]); + } + } +} + +- (id)init { + self = [super init]; + if (self) { + paths = nil; + } + return self; +} + +- (NSSet*)paths {return paths;} +- (void)setPaths:(NSSet *)p { + if (paths != p) { +#if __has_feature(objc_arc) + paths = p; +#else + [paths release]; + paths = [p retain]; +#endif + [self calcBoundingRect]; + } +} + +- (NSRect)boundingRect { return boundingRect; } + +@synthesize styleTikz; + +- (id)copyWithZone:(NSZone*)zone { + Shape *cp = [[[self class] allocWithZone:zone] init]; + [cp setPaths:paths]; + [cp setStyleTikz:styleTikz]; + return cp; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [paths release]; + [styleTikz release]; + [super dealloc]; +#endif +} + +NSDictionary *shapeDictionary = nil; + ++ (void)addShapesInDir:(NSString*)shapeDir to:(NSMutableDictionary*)shapeDict { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *err = nil; + NSArray *files = [fileManager contentsOfDirectoryAtPath:shapeDir error:&err]; + + if (files != nil) { + NSString *nm; + for (NSString *f in files) { + if ([f hasSuffix:@".tikz"]) { + nm = [f substringToIndex:[f length]-5]; + TikzShape *sh = + [[TikzShape alloc] initWithTikzFile: + [shapeDir stringByAppendingPathComponent:f]]; + if (sh != nil) { + [shapeDict setObject:sh forKey:nm]; +#if ! __has_feature(objc_arc) + [sh release]; +#endif + } + } + } + } +} + ++ (void)refreshShapeDictionary { + Shape *shapes[5] = { + [[CircleShape alloc] init], + [[RectangleShape alloc] init], + [[DiamondShape alloc] init], + [[RegularPolyShape alloc] initWithSides:3 rotation:0], + [[RegularPolyShape alloc] initWithSides:3 rotation:180]}; + NSMutableDictionary *shapeDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + shapes[0], SHAPE_CIRCLE, + shapes[1], SHAPE_RECTANGLE, + shapes[2], SHAPE_DIAMOND, + shapes[3], SHAPE_UP_TRIANGLE, + shapes[4], SHAPE_DOWN_TRIANGLE, + nil]; +#if ! __has_feature(objc_arc) + for (int i = 0; i<5; ++i) [shapes[i] release]; +#endif + + NSString *systemShapeDir = [[SupportDir systemSupportDir] stringByAppendingPathComponent:@"shapes"]; + NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; + + [Shape addShapesInDir:systemShapeDir to:shapeDict]; + [Shape addShapesInDir:userShapeDir to:shapeDict]; + + NSDictionary *oldShapeDictionary = shapeDictionary; + shapeDictionary = shapeDict; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"ShapeDictionaryReplaced" + object:self]; + +#if ! __has_feature(objc_arc) + [oldShapeDictionary release]; +#endif +} + ++ (NSDictionary*)shapeDictionary { + if (shapeDictionary == nil) [Shape refreshShapeDictionary]; + return shapeDictionary; +} + ++ (Shape*)shapeForName:(NSString*)shapeName { + Shape *s = [[[self shapeDictionary] objectForKey:shapeName] copy]; +#if __has_feature(objc_arc) + return s; +#else + return [s autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/ShapeNames.h b/tikzit-1/src/common/ShapeNames.h new file mode 100644 index 0000000..66ecfb1 --- /dev/null +++ b/tikzit-1/src/common/ShapeNames.h @@ -0,0 +1,27 @@ +// +// ShapeNames.h +// TikZiT +// +// Copyright 2011 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. +// + +#define SHAPE_CIRCLE @"circle" +#define SHAPE_RECTANGLE @"rectangle" +#define SHAPE_UP_TRIANGLE @"up triangle" +#define SHAPE_DOWN_TRIANGLE @"down triangle" +#define SHAPE_DIAMOND @"diamond" + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/StyleManager.h b/tikzit-1/src/common/StyleManager.h new file mode 100644 index 0000000..bc920e7 --- /dev/null +++ b/tikzit-1/src/common/StyleManager.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "NodeStyle.h" +#import "EdgeStyle.h" + +@interface StyleManager: NSObject <NSCopying> { + NSMutableArray *nodeStyles; + NSMutableArray *edgeStyles; +} + ++ (StyleManager*) manager; +- (id) init; + +@property (readonly) NSArray *nodeStyles; +@property (readonly) NSArray *edgeStyles; + +// only for use by loading code +- (void) _setNodeStyles:(NSMutableArray*)styles; +- (void) _setEdgeStyles:(NSMutableArray*)styles; + +- (NodeStyle*) nodeStyleForName:(NSString*)name; +- (EdgeStyle*) edgeStyleForName:(NSString*)name; + +- (void) addNodeStyle:(NodeStyle*)style; +- (void) removeNodeStyle:(NodeStyle*)style; +- (void) addEdgeStyle:(EdgeStyle*)style; +- (void) removeEdgeStyle:(EdgeStyle*)style; + +- (void) updateFromManager:(StyleManager*)manager; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/StyleManager.m b/tikzit-1/src/common/StyleManager.m new file mode 100644 index 0000000..05c6c86 --- /dev/null +++ b/tikzit-1/src/common/StyleManager.m @@ -0,0 +1,378 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "StyleManager.h" + +@implementation StyleManager + +- (void) nodeStylePropertyChanged:(NSNotification*)n { + if ([[[n userInfo] objectForKey:@"propertyName"] isEqual:@"name"]) { + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [n object], @"style", + [[n userInfo] objectForKey:@"oldValue"], @"oldName", + nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleRenamed" + object:self + userInfo:userInfo]; + } +} + +- (void) ignoreAllNodeStyles { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"NodeStylePropertyChanged" + object:nil]; +} + +- (void) ignoreNodeStyle:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"NodeStylePropertyChanged" + object:style]; +} + +- (void) listenToNodeStyle:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:style]; +} + +- (void) edgeStylePropertyChanged:(NSNotification*)n { + if ([[[n userInfo] objectForKey:@"propertyName"] isEqual:@"name"]) { + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [n object], @"style", + [[n userInfo] objectForKey:@"oldValue"], @"oldName", + nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleRenamed" + object:self + userInfo:userInfo]; + } +} + +- (void) ignoreAllEdgeStyles { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"EdgeStylePropertyChanged" + object:nil]; +} + +- (void) ignoreEdgeStyle:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"EdgeStylePropertyChanged" + object:style]; +} + +- (void) listenToEdgeStyle:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:style]; +} + ++ (StyleManager*) manager { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id) init { + self = [super init]; + + if (self) { + // we lazily load the default styles, since they may not be needed + nodeStyles = nil; + edgeStyles = nil; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#if ! __has_feature(objc_arc) + [nodeStyles release]; + [edgeStyles release]; + + [super dealloc]; +#endif +} + +- (void) loadDefaultEdgeStyles { +#if ! __has_feature(objc_arc) + [edgeStyles release]; +#endif + edgeStyles = [[NSMutableArray alloc] initWithCapacity:3]; + + EdgeStyle *simple = [EdgeStyle defaultEdgeStyleWithName:@"simple"]; + [simple setThickness:2.0f]; + [self listenToEdgeStyle:simple]; + + EdgeStyle *arrow = [EdgeStyle defaultEdgeStyleWithName:@"arrow"]; + [arrow setThickness:2.0f]; + [arrow setDecorationStyle:ED_Arrow]; + [self listenToEdgeStyle:arrow]; + + EdgeStyle *tick = [EdgeStyle defaultEdgeStyleWithName:@"tick"]; + [tick setThickness:2.0f]; + [tick setDecorationStyle:ED_Tick]; + [self listenToEdgeStyle:tick]; + + [edgeStyles addObject:simple]; + [edgeStyles addObject:arrow]; + [edgeStyles addObject:tick]; +} + +- (void) loadDefaultNodeStyles { +#if ! __has_feature(objc_arc) + [nodeStyles release]; +#endif + nodeStyles = [[NSMutableArray alloc] initWithCapacity:3]; + + NodeStyle *rn = [NodeStyle defaultNodeStyleWithName:@"rn"]; + [rn setStrokeThickness:2]; + [rn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [rn setFillColorRGB:[ColorRGB colorWithFloatRed:1 green:0 blue:0]]; + [self listenToNodeStyle:rn]; + + NodeStyle *gn = [NodeStyle defaultNodeStyleWithName:@"gn"]; + [gn setStrokeThickness:2]; + [gn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [gn setFillColorRGB:[ColorRGB colorWithFloatRed:0 green:1 blue:0]]; + [self listenToNodeStyle:gn]; + + NodeStyle *yn = [NodeStyle defaultNodeStyleWithName:@"yn"]; + [yn setStrokeThickness:2]; + [yn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [yn setFillColorRGB:[ColorRGB colorWithFloatRed:1 green:1 blue:0]]; + [self listenToNodeStyle:yn]; + + [nodeStyles addObject:rn]; + [nodeStyles addObject:gn]; + [nodeStyles addObject:yn]; +} + +- (void) postNodeStyleAdded:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleAdded" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postNodeStyleRemoved:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleRemoved" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postEdgeStyleAdded:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleAdded" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postEdgeStyleRemoved:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleRemoved" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postNodeStylesReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStylesReplaced" object:self]; +} + +- (void) postEdgeStylesReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStylesReplaced" object:self]; +} + +- (NSArray*) nodeStyles { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + return nodeStyles; +} + +- (NSArray*) edgeStyles { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + return edgeStyles; +} + +- (void) _setNodeStyles:(NSMutableArray*)styles { + [self ignoreAllNodeStyles]; +#if ! __has_feature(objc_arc) + [nodeStyles release]; + [styles retain]; +#endif + nodeStyles = styles; + for (NodeStyle *style in styles) { + [self listenToNodeStyle:style]; + } + [self postNodeStylesReplaced]; +} + +- (void) _setEdgeStyles:(NSMutableArray*)styles { + [self ignoreAllEdgeStyles]; +#if ! __has_feature(objc_arc) + [edgeStyles release]; + [styles retain]; +#endif + edgeStyles = styles; + for (EdgeStyle *style in styles) { + [self listenToEdgeStyle:style]; + } + [self postEdgeStylesReplaced]; +} + +- (NodeStyle*) nodeStyleForName:(NSString*)name { + for (NodeStyle *s in nodeStyles) { + if ([[s name] isEqualToString:name]) { + return s; + } + } + + return nil; +} + +- (void) addNodeStyle:(NodeStyle*)style { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + [nodeStyles addObject:style]; + [self listenToNodeStyle:style]; + [self postNodeStyleAdded:style]; +} + +- (void) removeNodeStyle:(NodeStyle*)style { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + + [self ignoreNodeStyle:style]; +#if ! __has_feature(objc_arc) + [style retain]; +#endif + [nodeStyles removeObject:style]; + [self postNodeStyleRemoved:style]; +#if ! __has_feature(objc_arc) + [style release]; +#endif +} + +- (EdgeStyle*) edgeStyleForName:(NSString*)name { + for (EdgeStyle *s in edgeStyles) { + if ([[s name] isEqualToString:name]) { + return s; + } + } + + return nil; +} + +- (void) addEdgeStyle:(EdgeStyle*)style { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + [edgeStyles addObject:style]; + [self listenToEdgeStyle:style]; + [self postEdgeStyleAdded:style]; +} + +- (void) removeEdgeStyle:(EdgeStyle*)style { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + + [self ignoreEdgeStyle:style]; +#if ! __has_feature(objc_arc) + [style retain]; +#endif + [edgeStyles removeObject:style]; + [self postEdgeStyleRemoved:style]; +#if ! __has_feature(objc_arc) + [style release]; +#endif +} + +- (void) updateFromManager:(StyleManager*)m { + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[[m nodeStyles] count]]; + for (NodeStyle *style in [m nodeStyles]) { + NodeStyle *currentStyle = [self nodeStyleForName:[style name]]; + if (currentStyle != nil) { + [currentStyle updateFromStyle:style]; + [ns addObject:currentStyle]; + } else { +#if __has_feature(objc_arc) + [ns addObject:[style copy]]; +#else + [ns addObject:[[style copy] autorelease]]; +#endif + } + } + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[[m edgeStyles] count]]; + for (EdgeStyle *style in [m edgeStyles]) { + EdgeStyle *currentStyle = [self edgeStyleForName:[style name]]; + if (currentStyle != nil) { + [currentStyle updateFromStyle:style]; + [es addObject:currentStyle]; + } else { +#if __has_feature(objc_arc) + [es addObject:[style copy]]; +#else + [es addObject:[[style copy] autorelease]]; +#endif + } + } + [self _setNodeStyles:ns]; + [self _setEdgeStyles:es]; +} + +- (id) copyWithZone:(NSZone*)zone { + StyleManager *m = [[StyleManager allocWithZone:zone] init]; + + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[nodeStyles count]]; + for (NodeStyle *style in nodeStyles) { +#if __has_feature(objc_arc) + [ns addObject:[style copyWithZone:zone]]; +#else + [ns addObject:[[style copyWithZone:zone] autorelease]]; +#endif + } + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[edgeStyles count]]; + for (EdgeStyle *style in edgeStyles) { +#if __has_feature(objc_arc) + [es addObject:[style copyWithZone:zone]]; +#else + [es addObject:[[style copyWithZone:zone] autorelease]]; +#endif + } + [m _setNodeStyles:ns]; + [m _setEdgeStyles:es]; + + return m; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/SupportDir.h b/tikzit-1/src/common/SupportDir.h new file mode 100644 index 0000000..30ccbcb --- /dev/null +++ b/tikzit-1/src/common/SupportDir.h @@ -0,0 +1,36 @@ +// +// SupportDir.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> + + +@interface SupportDir : NSObject { +} + ++ (void)createUserSupportDir; ++ (NSString*)userSupportDir; ++ (NSString*)systemSupportDir; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/SupportDir.m b/tikzit-1/src/common/SupportDir.m new file mode 100644 index 0000000..22fed1b --- /dev/null +++ b/tikzit-1/src/common/SupportDir.m @@ -0,0 +1,65 @@ +// +// SupportDir.m +// TikZiT +// +// Copyright 2011 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 "SupportDir.h" + +#ifndef __APPLE__ +#import <glib.h> +#import "stat.h" +#endif + +@implementation SupportDir + ++ (NSString*)userSupportDir { +#ifdef __APPLE__ + return [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES) + objectAtIndex:0] stringByAppendingPathComponent:@"TikZiT"]; +#else + return [NSString stringWithFormat:@"%s/tikzit", g_get_user_config_dir ()]; +#endif +} + ++ (NSString*)systemSupportDir { +#ifdef __APPLE__ + return [[NSBundle mainBundle] resourcePath]; +#else + return @TIKZITSHAREDIR; +#endif +} + ++ (void)createUserSupportDir { +#ifdef __APPLE__ + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager createDirectoryAtPath:[SupportDir userSupportDir] + withIntermediateDirectories:YES + attributes:nil + error:NULL]; +#else + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([[SupportDir userSupportDir] UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler+Parser.h b/tikzit-1/src/common/TikzGraphAssembler+Parser.h new file mode 100644 index 0000000..c9391a9 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler+Parser.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * TikzGraphAssember+Parser.h + * + * This file exposes some TikzGraphAssembler functions + * that are only of use to the parser. + */ + +#import "TikzGraphAssembler.h" + +@interface TikzGraphAssembler (Parser) +- (Graph*) graph; +/** Store a node so that it can be looked up by name later */ +- (void) addNodeToMap:(Node*)n; +/** Get a previously-stored node by name */ +- (Node*) nodeWithName:(NSString*)name; +- (void) reportError:(const char *)message atLocation:(YYLTYPE*)yylloc; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler.h b/tikzit-1/src/common/TikzGraphAssembler.h new file mode 100644 index 0000000..3403969 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler.h @@ -0,0 +1,115 @@ +// +// TikzGraphAssembler.h +// 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 <Foundation/Foundation.h> +#import "Graph.h" + +/** + * Parses (a subset of) tikz code and produces the corresponding Graph + * + * A note on errors: + * If parsing fails and a non-NULL error argument is given, it will be + * populated with an error with domain TZErrorDomain and code TZ_ERR_PARSE + * (see NSError+Tikzit.h). + * + * This will have a description set, typically something like + * "syntax error, unexpected [, expecting (" + * It may also have the following keys (it will have all or none of these), + * where numbers are stored using NSNumber: + * - startLine: the line (starting at 1) containing the first character + * of the bad token + * - startColumn: the column (starting at 1; tabs count for 1) of the first + * character of the bad token + * - endLine: the line (starting at 1) containing the last character + * of the bad token + * - endColumn: the column (starting at 1; tabs count for 1) of the last + * character of the bad token + * - syntaxString: an excerpt of the input string (typically the contents + * from startLine to endLine) providing some context + * - tokenOffset: the character offset (starting at 0) of the bad token + * within syntaxString + * - tokenLength: the character length (including newlines) of the bad token + * within syntaxString + */ +@interface TikzGraphAssembler : NSObject { + const char *tikzStr; + Graph *graph; + void *scanner; + NSMutableDictionary *nodeMap; + NSError *lastError; +} + +/** + * Parse tikz and place the result in gr + * + * Note that the graph must be empty; this might be used from an init + * method, for example, although don't forget that you can return a + * different object in init methods, providing you get the allocation + * right. + * + * @param tikz the tikz string to parse + * @param gr the graph to store the result in (must be empty, non-nil) + * @param e a location to store an error if parsing fails (may be NULL) + * @return YES if parsing succeeded, NO otherwise + */ ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr error:(NSError**)e; +/** + * Overload for -[parseTikz:forGraph:error:] with the error set to NULL + */ ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr; +/** + * Parse tikz + * + * @param tikz the tikz string to parse + * @param e a location to store an error if parsing fails (may be NULL) + * @return a Graph object if parsing succeeded, nil otherwise + */ ++ (Graph*) parseTikz:(NSString*)tikz error:(NSError**)e; +/** + * Overload for -[parseTikz:error:] with the error set to NULL + */ ++ (Graph*) parseTikz:(NSString*)tikz; +/** + * Validate a property string or value + * + * Wraps the string in "{" and "}" and checks it lexes completely; in other + * words, makes sure that "{" and "}" are balanced (ignoring escaped versions). + * @param tikz the string to validate + * @return YES if the string can be used as a property name or value, NO + * otherwise + */ ++ (BOOL)validateTikzPropertyNameOrValue:(NSString*)tikz; + +/** + * Validate an edge anchor + * + * Checks that the given string will successfully lex if used as an anchor for + * and edge + * @param tikz the string to validate + * @return YES if the string can be used as an edge anchor, NO otherwise + */ ++ (BOOL)validateTikzEdgeAnchor:(NSString*)tikz; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler.m b/tikzit-1/src/common/TikzGraphAssembler.m new file mode 100644 index 0000000..c5d2811 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler.m @@ -0,0 +1,310 @@ +// +// TikzGraphAssembler.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 "TikzGraphAssembler.h" +#import "tikzparserdefs.h" +#import "tikzparser.h" +#import "TikzGraphAssembler+Parser.h" +#import "tikzlexer.h" +#import "NSError+Tikzit.h" + +@implementation TikzGraphAssembler + +- (id)init { +#if ! __has_feature(objc_arc) + [self release]; +#endif + return nil; +} + +- (id)initWithGraph:(Graph*)g { + self = [super init]; + if (self) { +#if __has_feature(objc_arc) + graph = g; +#else + graph = [g retain]; +#endif + nodeMap = [[NSMutableDictionary alloc] init]; + yylex_init (&scanner); + yyset_extra(self, scanner); + } + return self; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [graph release]; + [nodeMap release]; + [lastError release]; + yylex_destroy (scanner); + [super dealloc]; +#endif +} + +- (BOOL) parseTikz:(NSString*)t error:(NSError**)error { +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + + tikzStr = [t UTF8String]; + yy_scan_string(tikzStr, scanner); + int result = yyparse(scanner); + tikzStr = NULL; + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + + if (result == 0) { + return YES; + } else { + if (error) { + if (lastError) { +#if __has_feature(objc_arc) + *error = lastError; +#else + *error = [[lastError retain] autorelease]; +#endif + } else if (result == 1) { + *error = [NSError errorWithMessage:@"Syntax error" + code:TZ_ERR_PARSE]; + } else if (result == 2) { + *error = [NSError errorWithMessage:@"Insufficient memory" + code:TZ_ERR_PARSE]; + } else { + *error = [NSError errorWithMessage:@"Unknown error" + code:TZ_ERR_PARSE]; + } + } + return NO; + } +} + ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr { + return [self parseTikz:tikz forGraph:gr error:NULL]; +} ++ (Graph*) parseTikz:(NSString*)tikz error:(NSError**)e { + Graph *gr = [[Graph alloc] init]; + if ([self parseTikz:tikz forGraph:gr error:e]) { +#if __has_feature(objc_arc) + return gr; +#else + return [gr autorelease]; +#endif + } else { +#if ! __has_feature(objc_arc) + [gr release]; +#endif + return nil; + } +} ++ (Graph*) parseTikz:(NSString*)tikz { + return [self parseTikz:tikz error:NULL]; +} + ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr error:(NSError**)error { + if([tikz length] == 0) { + // empty string -> empty graph + return YES; + } + + TikzGraphAssembler *assembler = [[self alloc] initWithGraph:gr]; + BOOL success = [assembler parseTikz:tikz error:error]; +#if ! __has_feature(objc_arc) + [assembler release]; +#endif + return success; +} + ++ (BOOL)validateTikzPropertyNameOrValue:(NSString*)tikz { + BOOL valid; + + NSString * testTikz = [NSString stringWithFormat: @"{%@}", tikz]; + + void *scanner; + yylex_init (&scanner); + yyset_extra(nil, scanner); + yy_scan_string([testTikz UTF8String], scanner); + YYSTYPE lval; + YYLTYPE lloc; + int result = yylex(&lval, &lloc, scanner); + valid = (result == DELIMITEDSTRING) && + (yyget_leng(scanner) == [testTikz length]); + yylex_destroy(scanner); + + return valid; +} + ++ (BOOL)validateTikzEdgeAnchor:(NSString*)tikz { + if ([tikz length] == 0) + return YES; + + BOOL valid = YES; + + NSString * testTikz = [NSString stringWithFormat: @"(1.%@)", tikz]; + + void *scanner; + yylex_init (&scanner); + yyset_extra(nil, scanner); + yy_scan_string([testTikz UTF8String], scanner); + YYSTYPE lval; + YYLTYPE lloc; + valid = valid && (yylex(&lval, &lloc, scanner) == LEFTPARENTHESIS); + valid = valid && (yylex(&lval, &lloc, scanner) == REFSTRING); + valid = valid && (yylex(&lval, &lloc, scanner) == FULLSTOP); + valid = valid && (yylex(&lval, &lloc, scanner) == REFSTRING); + valid = valid && (yylex(&lval, &lloc, scanner) == RIGHTPARENTHESIS); + valid = valid && (lloc.last_column == [testTikz length]); + yylex_destroy(scanner); + + return valid; +} + +@end + +@implementation TikzGraphAssembler (Parser) +- (Graph*)graph { return graph; } + +- (void)addNodeToMap:(Node*)n { + [nodeMap setObject:n forKey:[n name]]; +} + +- (Node*)nodeWithName:(NSString*)name { + return [nodeMap objectForKey:name]; +} + +- (void) setLastError:(NSError*)error { +#if ! __has_feature(objc_arc) + [error retain]; + [lastError release]; +#endif + lastError = error; +} + +- (void) reportError:(const char *)message atLocation:(YYLTYPE*)yylloc { + NSString *nsmsg = [NSString stringWithUTF8String:message]; + + const char *first_line_start = find_start_of_nth_line ( + tikzStr, yylloc->first_line - 1); + const char *last_line_start = find_start_of_nth_line ( + first_line_start, yylloc->last_line - yylloc->first_line); + const char *last_line_end = last_line_start; + while (*last_line_end && *last_line_end != '\n') { + // points to just after end of last line + ++last_line_end; + } + + size_t context_len = last_line_end - first_line_start; + size_t token_offset = yylloc->first_column - 1; + size_t token_len = ((last_line_start - first_line_start) + yylloc->last_column) - token_offset; + + if (token_offset + token_len > context_len) { + // error position state is corrupted + NSLog(@"Got bad error state for error \"%s\": start(%i,%i), end(%i,%i)", + message, + yylloc->first_line, + yylloc->first_column, + yylloc->last_line, + yylloc->last_column); + [self setLastError:[NSError errorWithMessage:nsmsg + code:TZ_ERR_PARSE]]; + } else { + char *context = malloc (context_len + 1); + strncpy (context, first_line_start, context_len); + *(context + context_len) = '\0'; + + NSDictionary *userInfo = + [NSDictionary dictionaryWithObjectsAndKeys: + nsmsg, + NSLocalizedDescriptionKey, + [NSNumber numberWithInt:yylloc->first_line], + @"startLine", + [NSNumber numberWithInt:yylloc->first_column], + @"startColumn", + [NSNumber numberWithInt:yylloc->last_line], + @"endLine", + [NSNumber numberWithInt:yylloc->last_column], + @"endColumn", + [NSString stringWithUTF8String:context], + @"syntaxString", + [NSNumber numberWithInt:token_offset], + @"tokenStart", + [NSNumber numberWithInt:token_len], + @"tokenLength", + nil]; + [self setLastError: + [NSError errorWithDomain:TZErrorDomain + code:TZ_ERR_PARSE + userInfo:userInfo]]; + + // we can now freely edit context string + // we only bother printing out the first line + if (yylloc->last_line > yylloc->first_line) { + char *nlp = strchr(context, '\n'); + if (nlp) { + *nlp = '\0'; + context_len = nlp - context; + NSAssert2(token_offset < context_len, @"token_offset (%lu) < context_len (%lu)", token_offset, context_len); + if (token_offset + token_len > context_len) { + token_len = context_len - token_offset; + } + } else { + NSLog(@"Didn't find any newlines in context string!"); + } + } + size_t token_col_offset = 0; + size_t token_col_len = 0; + for (int i = 0; i < token_offset; ++i) { + if (*(context + i) == '\t') + token_col_offset += 8; + else + ++token_col_offset; + } + for (int i = token_offset; i < token_offset + token_len; ++i) { + if (*(context + i) == '\t') + token_col_len += 8; + else + ++token_col_len; + } + NSString *pointerLinePadding = + [@"" stringByPaddingToLength:token_col_offset + withString:@" " + startingAtIndex:0]; + NSString *pointerLineCarets = + [@"" stringByPaddingToLength:token_col_len + withString:@"^" + startingAtIndex:0]; + NSLog(@"Parse error on line %i, starting at %i: %s\n%s\n%@%@", + yylloc->first_line, + yylloc->first_column, + message, + context, + pointerLinePadding, + pointerLineCarets); + free (context); + } +} +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzShape.h b/tikzit-1/src/common/TikzShape.h new file mode 100644 index 0000000..6a91f91 --- /dev/null +++ b/tikzit-1/src/common/TikzShape.h @@ -0,0 +1,37 @@ +// +// TikzShape.h +// TikZiT +// +// Copyright 2011 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 <Foundation/Foundation.h> +#import "Shape.h" + +@interface TikzShape : Shape { + NSString *tikzSrc; +} + +@property (copy) NSString *tikzSrc; + +- (id)initWithTikzFile:(NSString*)file; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzShape.m b/tikzit-1/src/common/TikzShape.m new file mode 100644 index 0000000..555a7df --- /dev/null +++ b/tikzit-1/src/common/TikzShape.m @@ -0,0 +1,70 @@ +// +// TikzShape.m +// TikZiT +// +// Copyright 2011 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 "TikzShape.h" +#import "Graph.h" + +@implementation TikzShape + +@synthesize tikzSrc; + +- (id)initWithTikzFile:(NSString*)file { + self = [super init]; + if (self) { + NSString *tikz = [NSString stringWithContentsOfFile:file + encoding:NSUTF8StringEncoding + error:NULL]; + if (tikz == nil) return nil; + + tikzSrc = [tikz copy]; + + Graph *graph = [Graph graphFromTikz:tikz]; + if (graph == nil) return nil; + + NSRect graphBounds = ([graph hasBoundingBox]) ? [graph boundingBox] : [graph bounds]; + + float sz = 0.5f; + + // the "screen" coordinate space fits in the shape bounds + Transformer *t = [Transformer transformer]; + float width_ratio = (2*sz) / graphBounds.size.width; + float height_ratio = (2*sz) / graphBounds.size.height; + [t setScale:MIN(width_ratio, height_ratio)]; + NSRect bds = [t rectToScreen:graphBounds]; + NSPoint shift = NSMakePoint(-NSMidX(bds), + -NSMidY(bds)); + [t setOrigin:shift]; + [graph applyTransformer:t]; +#if __has_feature(objc_arc) + paths = [graph pathCover]; +#else + paths = [[graph pathCover] retain]; +#endif + } + return self; +} + + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Transformer.h b/tikzit-1/src/common/Transformer.h new file mode 100644 index 0000000..1b0108a --- /dev/null +++ b/tikzit-1/src/common/Transformer.h @@ -0,0 +1,154 @@ +// +// Transformer.h +// 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 <Foundation/Foundation.h> + +extern float const PIXELS_PER_UNIT; + +/*! + @class Transformer + @brief Do affine coordinate transforms between an abstract co-ordinate + space (such as the graph's) and the screen's. + + This currently allows zooming and panning. + */ +@interface Transformer : NSObject <NSCopying> { + NSPoint origin; + float x_scale; + float y_scale; +} + +/*! + @brief The screen co-ordinate of the abstract space origin. + */ +@property (assign) NSPoint origin; + +/*! + @brief The scale (from abstract space to screen space) + @detail This is the size of a single unit (a distance of 1.0) + of the abstract space on the screen. + + Around 50 is a reasonable value. + */ +@property (assign) float scale; + +/*! + @brief Whether co-ordinates are flipped about the X axis + @detail TikZ considers X co-ordinates to run left to right, + which is not necessarily how the screen views + them. + */ +@property (assign,getter=isFlippedAboutXAxis) BOOL flippedAboutXAxis; + +/*! + @brief Whether co-ordinates are flipped about the Y axis + @detail TikZ considers Y co-ordinates to run up the page, + which is not necessarily how the screen views + them. + */ +@property (assign,getter=isFlippedAboutYAxis) BOOL flippedAboutYAxis; + +/*! + @brief Transform a point from screen space to abstract space. + @param p a point in screen space. + @result A point in abstract space. + */ +- (NSPoint)fromScreen:(NSPoint)p; + +/*! + @brief Transform a point from abstract space to screen space. + @param p a point in abstract space. + @result A point in screen space. + */ +- (NSPoint)toScreen:(NSPoint)p; + +/*! + @brief Scale a distance from screen space to abstract space. + @param dist a distance in screen space. + @result A distance in abstract space. + */ +- (float)scaleFromScreen:(float)dist; + +/*! + @brief Scale a distance from abstract space to screen space. + @param dist a distance in abstract space. + @result A distance in screen space. + */ +- (float)scaleToScreen:(float)dist; + +/*! + @brief Scale a rectangle from screen space to abstract space. + @param r a rectangle in screen space. + @result A rectangle in abstract space. + */ +- (NSRect)rectFromScreen:(NSRect)r; + +/*! + @brief Scale a rectangle from abstract space to screen space. + @param r a rectangle in abstract space. + @result A rectangle in screen space. + */ +- (NSRect)rectToScreen:(NSRect)r; + +/*! + @brief Factory method to get an identity transformer. + @result A transformer. + */ ++ (Transformer*)transformer; + +/*! + @brief Factory method to get a transformer identical to another + @result A transformer. + */ ++ (Transformer*)transformerWithTransformer:(Transformer*)t; + +/*! + @brief Factory method to get a transformer. + @param o The screen co-ordinate of the abstract space origin + @param scale The scale (from abstract space to screen space) + @result A transformer. + */ ++ (Transformer*)transformerWithOrigin:(NSPoint)o andScale:(float)scale; + +/*! + @brief Get a global 'actual size' transformer. + @result A transformer. + */ ++ (Transformer*)defaultTransformer; + +/*! + @brief A transformer set up from two bounding rects. + + graphRect is made as large as possible while still fitting into screenRect. + @result A transformer. + */ ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutXAxis:(BOOL)flipX flippedAboutYAxis:(BOOL)flipY; + ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutXAxis:(BOOL)flipX; ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutYAxis:(BOOL)flipY; ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Transformer.m b/tikzit-1/src/common/Transformer.m new file mode 100644 index 0000000..2b56813 --- /dev/null +++ b/tikzit-1/src/common/Transformer.m @@ -0,0 +1,231 @@ +// +// Transformer.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 "Transformer.h" + +float const PIXELS_PER_UNIT = 50; + +@implementation Transformer + ++ (Transformer*)transformer { +#if __has_feature(objc_arc) + return [[Transformer alloc] init]; +#else + return [[[Transformer alloc] init] autorelease]; +#endif +} + ++ (Transformer*)transformerWithTransformer:(Transformer*)t { +#if __has_feature(objc_arc) + return [t copy]; +#else + return [[t copy] autorelease]; +#endif +} + ++ (Transformer*)transformerWithOrigin:(NSPoint)o andScale:(float)scale { + Transformer *trans = [self transformer]; + [trans setOrigin:o]; + [trans setScale:scale]; + return trans; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:NO + flippedAboutYAxis:NO]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutXAxis:(BOOL)flipX { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:flipX + flippedAboutYAxis:NO]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutYAxis:(BOOL)flipY { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:NO + flippedAboutYAxis:flipY]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutXAxis:(BOOL)flipAboutXAxis + flippedAboutYAxis:(BOOL)flipAboutYAxis { + + const float wscale = screenRect.size.width / graphRect.size.width; + const float hscale = screenRect.size.height / graphRect.size.height; + const float scale = (wscale < hscale) ? wscale : hscale; + const float xpad = (screenRect.size.width - (graphRect.size.width * scale)) / 2.0; + const float ypad = (screenRect.size.height - (graphRect.size.height * scale)) / 2.0; + + // if we are flipping, we need to calculate the origin from the opposite edge + const float gx = flipAboutYAxis ? -(graphRect.size.width + graphRect.origin.x) + : graphRect.origin.x; + const float gy = flipAboutXAxis ? -(graphRect.size.height + graphRect.origin.y) + : graphRect.origin.y; + const float origin_x = screenRect.origin.x - (gx * scale) + xpad; + const float origin_y = screenRect.origin.y - (gy * scale) + ypad; + + Transformer *trans = [self transformer]; + [trans setOrigin:NSMakePoint(origin_x, origin_y)]; + [trans setScale:scale]; + [trans setFlippedAboutXAxis:flipAboutXAxis]; + [trans setFlippedAboutYAxis:flipAboutYAxis]; + return trans; +} + +- (id) init { + self = [super init]; + + if (self) { + origin = NSZeroPoint; + x_scale = 1.0f; + y_scale = 1.0f; + } + + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + Transformer *cp = [[[self class] allocWithZone:zone] init]; + if (cp) { + cp->origin = origin; + cp->x_scale = x_scale; + cp->y_scale = y_scale; + } + return cp; +} + +- (NSPoint)origin { return origin; } +- (void)setOrigin:(NSPoint)o { + origin = o; +} + +- (float)scale { return ABS(x_scale); } +- (void)setScale:(float)s { + x_scale = (x_scale < 0.0) ? -s : s; + y_scale = (y_scale < 0.0) ? -s : s; +} + +- (BOOL)isFlippedAboutXAxis { + return y_scale < 0.0; +} + +- (void)setFlippedAboutXAxis:(BOOL)flip { + if (flip != [self isFlippedAboutXAxis]) { + y_scale *= -1; + } +} + +- (BOOL)isFlippedAboutYAxis { + return x_scale < 0.0; +} + +- (void)setFlippedAboutYAxis:(BOOL)flip { + if (flip != [self isFlippedAboutYAxis]) { + x_scale *= -1; + } +} + +- (NSPoint)fromScreen:(NSPoint)p { + NSPoint trans; + trans.x = (p.x - origin.x) / x_scale; + trans.y = (p.y - origin.y) / y_scale; + return trans; +} + +- (NSPoint)toScreen:(NSPoint)p { + NSPoint trans; + trans.x = (p.x * x_scale) + origin.x; + trans.y = (p.y * y_scale) + origin.y; + return trans; +} + +- (float)scaleFromScreen:(float)dist { + return dist / ABS(x_scale); +} + +- (float)scaleToScreen:(float)dist { + return dist * ABS(x_scale); +} + +- (NSRect)rectFromScreen:(NSRect)r { + NSRect r1; + r1.origin = [self fromScreen:r.origin]; + r1.size.width = [self scaleFromScreen:r.size.width]; + r1.size.height = [self scaleFromScreen:r.size.height]; + // if we're flipped, the origin will be at a different corner + if ([self isFlippedAboutYAxis]) { + r1.origin.x -= r1.size.width; + } + if ([self isFlippedAboutXAxis]) { + r1.origin.y -= r1.size.height; + } + return r1; +} + +- (NSRect)rectToScreen:(NSRect)r { + NSPoint o = r.origin; + // if we're flipped, the origin will be at a different corner + if ([self isFlippedAboutYAxis]) { + o.x = NSMaxX(r); + } + if ([self isFlippedAboutXAxis]) { + o.y = NSMaxY(r); + } + NSRect r1; + r1.origin = [self toScreen:o]; + r1.size.width = [self scaleToScreen:r.size.width]; + r1.size.height = [self scaleToScreen:r.size.height]; + return r1; +} + +- (BOOL)isEqual:(id)object { + Transformer *t = (Transformer*)object; + return ([t origin].x == [self origin].x && + [t origin].y == [self origin].y && + [t scale] == [self scale]); +} + +Transformer *defaultTransformer = nil; + ++ (Transformer*)defaultTransformer { + if (defaultTransformer == nil) { + defaultTransformer = [[Transformer alloc] init]; + [defaultTransformer setScale:PIXELS_PER_UNIT]; + } + return defaultTransformer; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/test/Makefile b/tikzit-1/src/common/test/Makefile new file mode 100644 index 0000000..d158d16 --- /dev/null +++ b/tikzit-1/src/common/test/Makefile @@ -0,0 +1,14 @@ +OBJC = gcc -MMD -MP -DSTAND_ALONE -DGNUSTEP -DGNUSTEP_BASE_LIBRARY=1 -DGNU_RUNTIME=1 -DGNUSTEP_BASE_LIBRARY=1 -fno-strict-aliasing -fPIC -Wall -DGSWARN -DGSDIAGNOSE -Wno-import -O0 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -fgnu-runtime -fconstant-string-class=NSConstantString -I. -I.. -I/users/alemer/GNUstep/Library/Headers -std=c99 -D_GNU_SOURCE -rdynamic -fgnu-runtime -L/users/alemer/GNUstep/Library/Libraries -L/usr/local/lib64 -L/usr/lib64 -lgnustep-base -lpthread -lobjc -lm + +maths_test_objects = test.m maths.m ../util.m +color_test_objects = test.m color.m ../ColorRGB.m ../util.m ../BasicMapTable.m ../RColor.m + +test: maths-test color-test + ./maths-test + ./color-test + +maths-test: $(maths_test_objects) + $(OBJC) $(maths_test_objects) -o $@ + +color-test: $(color_test_objects) + $(OBJC) $(color_test_objects) -o $@ diff --git a/tikzit-1/src/common/test/color.m b/tikzit-1/src/common/test/color.m new file mode 100644 index 0000000..48a6ff4 --- /dev/null +++ b/tikzit-1/src/common/test/color.m @@ -0,0 +1,80 @@ +// +// color.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 "test/test.h" +#import "ColorRGB.h" + +#ifdef STAND_ALONE +void runTests() { +#else +void testColor() { +#endif + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"color"); + + ColorRGB *red = [ColorRGB colorWithRed:255 green:0 blue:0]; + ColorRGB *lime = [ColorRGB colorWithRed:0 green:255 blue:0]; + ColorRGB *green = [ColorRGB colorWithRed:0 green:128 blue:0]; + TEST(@"Recognised red", + [red name] != nil && + [[red name] isEqualToString:@"Red"]); + TEST(@"Recognised lime", + [lime name] != nil && + [[lime name] isEqualToString:@"Lime"]); + TEST(@"Recognised green", + [green name] != nil && + [[green name] isEqualToString:@"Green"]); + + ColorRGB *floatRed = [ColorRGB colorWithFloatRed:1.0f green:0.0f blue:0.0f]; + ColorRGB *floatLime = [ColorRGB colorWithFloatRed:0.0f green:1.0f blue:0.0f]; + ColorRGB *floatGreen = [ColorRGB colorWithFloatRed:0.0f green:0.5f blue:0.0f]; + + TEST(@"Float red equal to int red", [floatRed isEqual:red]); + TEST(@"Float lime equal to int lime", [floatLime isEqual:lime]); + TEST(@"Float green equal to int green", [floatGreen isEqual:green]); + + TEST(@"Recognised float red", + [floatRed name] != nil && + [[floatRed name] isEqualToString:@"Red"]); + + TEST(@"Recognised float lime", + [floatLime name] != nil && + [[floatLime name] isEqualToString:@"Lime"]); + + TEST(@"Recognised float green", + [floatGreen name] != nil && + [[floatGreen name] isEqualToString:@"Green"]); + + [floatRed setRedFloat:0.99f]; + TEST(@"Nudged red, not recognised now", [floatRed name] == nil); + [floatRed setToClosestHashed]; + TEST(@"Set to closest hashed, reconised again", + [floatRed name] != nil && + [[floatRed name] isEqualToString:@"Red"]); + + TEST(@"Red has correct hex (ff0000)", [[red hexName] isEqualToString:@"hexcolor0xff0000"]); + TEST(@"Lime has correct hex (00ff00)", [[lime hexName] isEqualToString:@"hexcolor0x00ff00"]); + TEST(@"Green has correct hex (008000)", [[green hexName] isEqualToString:@"hexcolor0x008000"]); + + endTestBlock(@"color"); + [pool drain]; +} diff --git a/tikzit-1/src/common/test/common.m b/tikzit-1/src/common/test/common.m new file mode 100644 index 0000000..c9ac980 --- /dev/null +++ b/tikzit-1/src/common/test/common.m @@ -0,0 +1,34 @@ +// +// common.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 "test/test.h" +void testParser(); +void testColor(); +void testMaths(); + +void testCommon() { + startTestBlock(@"common"); + testParser(); + testColor(); + testMaths(); + endTestBlock(@"common"); +} diff --git a/tikzit-1/src/common/test/maths.m b/tikzit-1/src/common/test/maths.m new file mode 100644 index 0000000..a11e58e --- /dev/null +++ b/tikzit-1/src/common/test/maths.m @@ -0,0 +1,562 @@ +// +// TikZiT +// +// Copyright 2011 Alex Merry +// +// 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 "../util.h" + +#import "test.h" + +void testRectAroundPoints() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPoints"); + + NSRect rect = NSRectAroundPoints (NSZeroPoint, NSZeroPoint); + assertRectsEqual (@"(0,0) and (0,0)", rect, NSZeroRect); + + rect = NSRectAroundPoints (NSZeroPoint, NSMakePoint (1.0f, 1.0f)); + assertRectsEqual (@"(0,0) and (1,1)", rect, NSMakeRect (0.0f, 0.0f, 1.0f, 1.0f)); + + rect = NSRectAroundPoints (NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f)); + assertRectsEqual (@"(-1,1) and (1,-1)", rect, NSMakeRect (-1.0f, -1.0f, 2.0f, 2.0f)); + + endTestBlock(@"NSRectAroundPoints"); + [pool drain]; +} + +void testRectAroundPointsWithPadding() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPointsWithPadding"); + + NSRect rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.0f); + assertRectsEqual (@"(0,0) and (0,0); 0 padding", rect, NSZeroRect); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.2f); + assertRectsEqual (@"(0,0) and (0,0); 0.2 padding", rect, NSMakeRect (-0.2f, -0.2f, 0.4f, 0.4f)); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSMakePoint (1.0f, 1.0f), -0.2f); + assertRectsEqual (@"(0,0) and (1,1); -0.2 padding", rect, NSMakeRect (0.2f, 0.2f, 0.6f, 0.6f)); + + endTestBlock(@"NSRectAroundPointsWithPadding"); + [pool drain]; +} + +void testGoodAtan() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"good_atan"); + + assertFloatsEqual (@"0.0, 0.0", good_atan (0.0f, 0.0f), 0.0f); + assertFloatsEqual (@"0.0, 1.0", good_atan (0.0f, 1.0f), 0.5f * M_PI); + assertFloatsEqual (@"0.0, -1.0", good_atan (0.0f, -1.0f), 1.5f * M_PI); + assertFloatsEqual (@"1.0, 0.0", good_atan (1.0f, 0.0f), 0.0f); + assertFloatsEqual (@"1.0, 0.1", good_atan (1.0f, 0.1f), 0.0996687f); + + endTestBlock(@"good_atan"); + [pool drain]; +} + +void testBezierInterpolate() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"bezierInterpolate"); + + assertFloatsEqual (@"0.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.0f); + assertFloatsEqual (@"1.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (1.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.3f); + assertFloatsEqual (@"0.5, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.5f, 0.0f, 0.1f, 0.2f, 0.3f), 0.15f); + // FIXME: other tests + + endTestBlock(@"bezierInterpolate"); + [pool drain]; +} + +void testLineSegmentsIntersect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentsIntersect"); + + BOOL result = NO; + NSPoint intersection = NSMakePoint (-1.0f, -1.0f); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (1.0f, 1.0f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Cross at zero: has intersection", result); + assertPointsEqual (@"Cross at zero: intersection value", intersection, NSZeroPoint); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (-0.5f, -0.5f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Fail to cross at zero", !result); + + result = lineSegmentsIntersect (NSMakePoint (1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), NSMakePoint (1.0f, 0.0f), + &intersection); + TEST (@"Touch at one: has intersection", result); + assertPointsEqual (@"Touch at one: intersection value", intersection, NSMakePoint (1.0f, 0.0f)); + + endTestBlock(@"lineSegmentsIntersect"); + [pool drain]; +} + +void testLineSegmentIntersectsRect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsRect"); + + BOOL result = NO; + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), + NSZeroRect); + TEST (@"Zero rect; line touches zero", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (-0.1f, -0.1f), + NSZeroRect); + TEST (@"Zero rect; line short of zero", !result); + + NSRect rect = NSMakeRect (1.0f, 1.0f, 1.0f, 1.0f); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Line underneath", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Line to left", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 2.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line above", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line to right", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (0.9f, 0.9f), + rect); + TEST (@"Line short", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.1f, 1.1f), + NSMakePoint (1.9f, 1.9f), + rect); + TEST (@"Line inside", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.5f), + NSMakePoint (3.0f, 1.5f), + rect); + TEST (@"Horizontal line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.5f, 0.0f), + NSMakePoint (1.5f, 3.0f), + rect); + TEST (@"Vertical line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.5f, 1.0f), + NSMakePoint (2.0f, 2.5f), + rect); + TEST (@"Cut top and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.5f), + NSMakePoint (0.5f, 2.0f), + rect); + TEST (@"Cut bottom and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.5f), + NSMakePoint (2.5f, 2.0f), + rect); + TEST (@"Cut bottom and right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (2.0f, 3.0f), + rect); + TEST (@"Touch top left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (3.0f, 2.0f), + rect); + TEST (@"Touch bottom right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Along left side", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Along bottom side", result); + + endTestBlock(@"lineSegmentIntersectsRect"); + [pool drain]; +} + +struct line_bezier_test { + NSString *msg; + NSPoint lstart; + NSPoint lend; + NSPoint c0; + NSPoint c1; + NSPoint c2; + NSPoint c3; + BOOL expectedResult; + float expectedT; + NSPoint expectedIntersect; +}; + +static struct line_bezier_test line_bezier_tests[] = { + { + @"Outside box", + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.0f, 1.0f}, + {0.0f, 2.0f}, + {1.0f, 2.0f}, + {1.0f, 1.0f}, + NO, + -1.0f, + {0.0f, 0.0f} + }, + { + @"Single intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {219.0f, 178.0f}, + YES, + -0.4f, + {129.391693f, 92.705772f} + }, + { + @"Double intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {154.0f, 212.0f}, + YES, + -0.909f, + {170.740646f,194.990021f} + }, + { + @"Near miss", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {176.0f, 100.0f}, + {154.0f, 212.0f}, + NO, + -1.0f, + {0.0f,0.0f} + } +}; +static unsigned int n_line_bezier_tests = sizeof (line_bezier_tests) / sizeof (line_bezier_tests[0]); + +void testLineSegmentIntersectsBezier() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsBezier"); + + for (unsigned int i = 0; i < n_line_bezier_tests; ++i) { + NSPoint intersect; + BOOL result = lineSegmentIntersectsBezier ( + line_bezier_tests[i].lstart, + line_bezier_tests[i].lend, + line_bezier_tests[i].c0, + line_bezier_tests[i].c1, + line_bezier_tests[i].c2, + line_bezier_tests[i].c3, + &intersect); + if (result) { + if (line_bezier_tests[i].expectedT < 0.0f) { + assertPointsEqual (line_bezier_tests[i].msg, intersect, line_bezier_tests[i].expectedIntersect); + } else { + assertPointsEqual (line_bezier_tests[i].msg, intersect, + bezierInterpolateFull (line_bezier_tests[i].expectedT, line_bezier_tests[i].c0, line_bezier_tests[i].c1, line_bezier_tests[i].c2, line_bezier_tests[i].c3)); + } + } else { + if (line_bezier_tests[i].expectedResult) + fail (line_bezier_tests[i].msg); + else + pass (line_bezier_tests[i].msg); + } + } + +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); + endTestBlock(@"lineSegmentIntersectsBezier"); + [pool drain]; +} + +struct exit_point_test { + NSString *msg; + NSPoint rayStart; + float angle; + NSRect rect; + NSPoint expected; +}; + +static struct exit_point_test exit_point_tests[] = { + { + @"0.0 rads", + {0.0f, 0.0f}, + 0.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.0f} + }, + { + @"pi/2 rads", + {0.0f, 0.0f}, + M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, 1.0f} + }, + { + @"-pi/2 rads", + {0.0f, 0.0f}, + -M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, -1.0f} + }, + { + @"pi rads", + {0.0f, 0.0f}, + M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"-pi rads", + {0.0f, 0.0f}, + -M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"pi/4 rads", + {0.0f, 0.0f}, + M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 1.0f} + }, + { + @"3pi/4 rads", + {0.0f, 0.0f}, + (3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 1.0f} + }, + { + @"-pi/4 rads", + {0.0f, 0.0f}, + -M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, -1.0f} + }, + { + @"-3pi/4 rads", + {0.0f, 0.0f}, + (-3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -1.0f} + }, + { + @"pi/8 rads", + {0.0f, 0.0f}, + M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.414213562373095f} + }, + { + @"3pi/8 rads", + {0.0f, 0.0f}, + 3.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.414213562373095f, 1.0f} + }, + { + @"-5pi/8 rads", + {0.0f, 0.0f}, + -5.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-0.414213562373095f, -1.0f} + }, + { + @"-7pi/8 rads", + {0.0f, 0.0f}, + -7.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -0.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1)", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 0.0f}, {2.0f, 2.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"7pi/8 rads; origin (-2,2)", + {-2.0f, 2.0f}, + 7.0f * M_PI / 8.0f, + {{-3.0f, 1.0f}, {2.0f, 2.0f}}, + {-3.0f, 2.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 1.0f}, {1.0f, 1.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); S of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.5f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); E of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + -7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); leave through top", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.9f, 0.1f}, {1.0f, 1.0f}}, + {1.2414213562373f, 1.1f} + }, + { + @"0 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 0.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.5f, 1.0f} + } +}; +static unsigned int n_exit_point_tests = sizeof (exit_point_tests) / sizeof (exit_point_tests[0]); + +void testFindExitPointOfRay() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"findExitPointOfRay"); + + for (unsigned int i = 0; i < n_exit_point_tests; ++i) { + NSPoint exitPoint = findExitPointOfRay ( + exit_point_tests[i].rayStart, + exit_point_tests[i].angle, + exit_point_tests[i].rect); + assertPointsEqual (exit_point_tests[i].msg, exitPoint, exit_point_tests[i].expected); + } + + endTestBlock(@"findExitPointOfRay"); + [pool drain]; +} + +#ifdef STAND_ALONE +void runTests() { +#else +void testMaths() { +#endif + startTestBlock(@"maths"); + testRectAroundPoints(); + testRectAroundPointsWithPadding(); + testGoodAtan(); + testBezierInterpolate(); + testLineSegmentsIntersect(); + testLineSegmentIntersectsRect(); + testFindExitPointOfRay(); + testLineSegmentIntersectsBezier(); + endTestBlock(@"maths"); +} + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/test/parser.m b/tikzit-1/src/common/test/parser.m new file mode 100644 index 0000000..3346acd --- /dev/null +++ b/tikzit-1/src/common/test/parser.m @@ -0,0 +1,86 @@ +// +// parser.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 "test/test.h" +#import "TikzGraphAssembler.h" + + +#ifdef STAND_ALONE +void runTests() { +#else +void testParser() { +#endif + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"parser"); + + [TikzGraphAssembler setup]; + + NodeStyle *rn = [NodeStyle defaultNodeStyleWithName:@"rn"]; + NSArray *styles = [NSArray arrayWithObject:rn]; + + NSString *tikz = + @"\\begin{tikzpicture}[dotpic]" + @" \\begin{pgfonlayer}{foo}" //ignored + @" \\node [style=rn] (0) at (-2,3.4) {stuff{$\\alpha$ in here}};" + @" \\node (b) at (1,1) {};" + @" \\end{pgfonlayer}" //ignored + @" \\draw [bend right=20] (0) to node[tick]{-} (b.center);" + @"\\end{tikzpicture}"; + + TikzGraphAssembler *ga = [[TikzGraphAssembler alloc] init]; + TEST(@"Parsing TikZ", [ga parseTikz:tikz]); + + Graph *g = [ga graph]; + TEST(@"Graph is non-nil", g != nil); + TEST(@"Graph has correct number of nodes", [[g nodes] count]==2); + TEST(@"Graph has correct number of edges", [[g edges] count]==1); + + NSEnumerator *en = [[g nodes] objectEnumerator]; + Node *n; + Node *n1, *n2; + while ((n=[en nextObject])) { + [n attachStyleFromTable:styles]; + if ([n style] == rn) n1 = n; + else if ([n style] == nil) n2 = n; + } + + TEST(@"Styles attached correctly", n1!=nil && n2!=nil); + + TEST(@"Nodes labeled correctly", + [[n1 label] isEqualToString:@"stuff{$\\alpha$ in here}"] && + [[n2 label] isEqualToString:@""] + ); + + Edge *e1 = [[[g edges] objectEnumerator] nextObject]; + + TEST(@"Edge has edge node", [e1 edgeNode]!=nil); + TEST(@"Edge node labeled correctly", [[[e1 edgeNode] label] isEqualToString:@"-"]); +// NSString *sty = [[[[[e1 edgeNode] data] atoms] objectEnumerator] nextObject]; +// TEST(@"Edge node styled correctly", sty!=nil && [sty isEqualToString:@"tick"]); + + PUTS(@"Source anchor: %@",[e1 sourceAnchor]); + PUTS(@"Target anchor: %@",[e1 targetAnchor]); + + endTestBlock(@"parser"); + + [pool drain]; +} diff --git a/tikzit-1/src/common/test/test.h b/tikzit-1/src/common/test/test.h new file mode 100644 index 0000000..59dcdd4 --- /dev/null +++ b/tikzit-1/src/common/test/test.h @@ -0,0 +1,57 @@ +// +// test.h +// 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 <Foundation/Foundation.h> + +@interface Allocator : NSObject +{} +@end + +BOOL fuzzyCompare (float f1, float f2); +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2); + +void setColorEnabled(BOOL b); + +void pass(NSString *msg); +void fail(NSString *msg); +void TEST(NSString *msg, BOOL test); +void assertRectsEqual (NSString *msg, NSRect val, NSRect exp); +void assertPointsEqual (NSString *msg, NSPoint val, NSPoint exp); +void assertFloatsEqual (NSString *msg, float val, float exp); + +void startTests(); +void endTests(); + +void startTestBlock(NSString *name); +void endTestBlock(NSString *name); + +#define PUTS(fmt, ...) { \ + NSString *_str = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ + printf("%s\n", [_str UTF8String]); \ + [_str release]; } + +#define failFmt(fmt, ...) { \ + NSString *_fstr = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ + fail(_fstr); \ + [_fstr release]; } + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/test/test.m b/tikzit-1/src/common/test/test.m new file mode 100644 index 0000000..9afcd67 --- /dev/null +++ b/tikzit-1/src/common/test/test.m @@ -0,0 +1,175 @@ +// +// test.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 "test/test.h" + +static int PASSES; +static int FAILS; + +static int ALLOC_INSTANCES = 0; + +static BOOL colorEnabled = YES; +static int depth = 0; + +static NSString *RED, *GREEN, *BLUE, *OFF; + +static NSString *indents[6] = + {@"", @" ", @" ", @" ", + @" ", @" "}; + +#define INDENT ((depth >= 6) ? indents[5] : indents[depth]) + + +@implementation Allocator + ++ (id)alloc { + ++ALLOC_INSTANCES; + return [super alloc]; +} + +- (void)dealloc { + --ALLOC_INSTANCES; + [super dealloc]; +} + ++ (Allocator*)allocator { + return [[[Allocator alloc] init] autorelease]; +} + +@end + +BOOL fuzzyCompare(float f1, float f2) { + return (ABS(f1 - f2) <= 0.00001f * MAX(1.0f,MIN(ABS(f1), ABS(f2)))); +} + +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2) { + return fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); +} + +void pass(NSString *msg) { + PUTS(@"%@[%@PASS%@] %@", INDENT, GREEN, OFF, msg); + ++PASSES; +} + +void fail(NSString *msg) { + PUTS(@"%@[%@FAIL%@] %@", INDENT, RED, OFF, msg); + ++FAILS; +} + +void TEST(NSString *msg, BOOL test) { + if (test) { + pass (msg); + } else { + fail (msg); + } +} + +void assertRectsEqual (NSString *msg, NSRect r1, NSRect r2) { + BOOL equal = fuzzyCompare (r1.origin.x, r2.origin.x) && + fuzzyCompare (r1.origin.y, r2.origin.y) && + fuzzyCompare (r1.size.width, r2.size.width) && + fuzzyCompare (r1.size.height, r2.size.height); + if (equal) { + pass (msg); + } else { + failFmt(@"%@ (expected (%f,%f:%fx%f) but got (%f,%f:%fx%f))", + msg, + r2.origin.x, r2.origin.y, r2.size.width, r2.size.height, + r1.origin.x, r1.origin.y, r1.size.width, r1.size.height); + } +} + +void assertPointsEqual (NSString *msg, NSPoint p1, NSPoint p2) { + BOOL equal = fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); + if (equal) { + pass (msg); + } else { + failFmt(@"%@ (expected (%f,%f) but got (%f,%f)", + msg, + p2.x, p2.y, + p1.x, p1.y); + } +} + +void assertFloatsEqual (NSString *msg, float f1, float f2) { + if (fuzzyCompare (f1, f2)) { + pass (msg); + } else { + failFmt(@"%@ (expected %f but got %f", msg, f2, f1); + } +} + +void startTests() { + PASSES = 0; + FAILS = 0; +} + +void endTests() { + PUTS(@"Done testing. %@%d%@ passed, %@%d%@ failed.", + GREEN, PASSES, OFF, + RED, FAILS, OFF); +} + +void startTestBlock(NSString *name) { + PUTS(@"%@Starting %@%@%@ tests.", INDENT, BLUE, name, OFF); + ++depth; +} + +void endTestBlock(NSString *name) { + --depth; + PUTS(@"%@Done with %@%@%@ tests.", INDENT, BLUE, name, OFF); +} + +void setColorEnabled(BOOL b) { + colorEnabled = b; + if (b) { + RED = @"\033[31;1m"; + GREEN = @"\033[32;1m"; + BLUE = @"\033[36;1m"; + OFF = @"\033[0m"; + } else { + RED = @""; + GREEN = @""; + BLUE = @""; + OFF = @""; + } +} + +#ifdef STAND_ALONE +void runTests(); + +int main() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + setColorEnabled (NO); + startTests(); + + runTests(); + + endTests(); + + [pool drain]; + return 0; +} +#endif + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/tikzlexer.lm b/tikzit-1/src/common/tikzlexer.lm new file mode 100644 index 0000000..1e92f73 --- /dev/null +++ b/tikzit-1/src/common/tikzlexer.lm @@ -0,0 +1,170 @@ +%{ +/* + * Copyright 2010 Chris Heunen + * Copyright 2010-2013 Aleks Kissinger + * Copyright 2013 K. Johan Paulsson + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "tikzparserdefs.h" +#import "tikzparser.h" + +#define YY_USER_ACTION \ + yylloc->first_line = yylloc->last_line; \ + yylloc->first_column = yylloc->last_column + 1; \ + yylloc->last_column = yylloc->first_column + yyleng - 1; + +%} + +%option reentrant bison-bridge bison-locations 8bit +%option nounput +%option yylineno +%option noyywrap +%option header-file="common/tikzlexer.h" +%option extra-type="TikzGraphAssembler *" + + +%s props +%s xcoord +%s ycoord +%s noderef + +FLOAT \-?[0-9]*(\.[0-9]+)? + +%% + + /* whitespace is ignored, except for position counting; we don't + count formfeed and vtab as whitespace, because it's not obvious + how they should be dealt with and no-one actually uses them */ + + /* lex will take the longest-matching string */ +<INITIAL,xcoord,ycoord,props,noderef>\r\n|\r|\n { + yylloc->first_line += 1; + yylloc->last_line = yylloc->first_line; + yylloc->first_column = yylloc->last_column = 0; +} +<INITIAL,xcoord,ycoord,props,noderef>[\t ]+ { } + +\\begin\{tikzpicture\} { return BEGIN_TIKZPICTURE_CMD; } +\\end\{tikzpicture\} { return END_TIKZPICTURE_CMD; } +\\begin\{pgfonlayer\} { return BEGIN_PGFONLAYER_CMD; } +\\end\{pgfonlayer\} { return END_PGFONLAYER_CMD; } +\\draw { return DRAW_CMD; } +\\node { return NODE_CMD; } +\\path { return PATH_CMD; } +rectangle { return RECTANGLE; } +node { return NODE; } +at { return AT; } +to { return TO; } +; { return SEMICOLON; } + +\([ ]*{FLOAT}[ ]*,[ ]*{FLOAT}[ ]*\) { + yylloc->last_column = yylloc->first_column + 1; + yyless(1); + BEGIN(xcoord); +} +<xcoord>{FLOAT} { + yylval->pt.x=(float)strtod(yytext,NULL); + BEGIN(ycoord); +} +<ycoord>, { } +<ycoord>{FLOAT} { + yylval->pt.y=(float)strtod(yytext,NULL); +} +<ycoord>\) { + BEGIN(INITIAL); + return COORD; +} + + /* when we see "[", change parsing mode */ +\[ /*syntaxhlfix]*/ { + BEGIN(props); + return LEFTBRACKET; +} +<props>= { return EQUALS; } +<props>, { return COMMA; } + /* technically, it is possible to have newlines in the middle of + property names or values, but in practice this is unlikely and + screws up our line counting */ +<props>[^=,\{\] \t\n]([^=,\{\]\n]*[^=,\{\] \t\n])? { + yylval->nsstr=[NSString stringWithUTF8String:yytext]; + return PROPSTRING; +} +<props>\] { + BEGIN(INITIAL); + return RIGHTBRACKET; +} + +\( { + BEGIN(noderef); + return LEFTPARENTHESIS; +} +<noderef>\. { + return FULLSTOP; +} + /* we assume node names (and anchor names) never contain + newlines */ +<noderef>[^\.\{\)\n]+ { + yylval->nsstr=[NSString stringWithUTF8String:yytext]; + return REFSTRING; +} +<noderef>\) { + BEGIN(INITIAL); + return RIGHTPARENTHESIS; +} + +<INITIAL,props>\{ { + NSMutableString *buf = [NSMutableString string]; + unsigned int brace_depth = 1; + unsigned int escape = 0; + while (1) { + char c = input(yyscanner); + // eof reached before closing brace + if (c == '\0' || c == EOF) { + return UNCLOSED_DELIM_STR; + } + + yylloc->last_column += 1; + yyleng += 1; + if (escape) { + escape = 0; + } else if (c == '\\') { + escape = 1; + } else if (c == '{') { + brace_depth++; + } else if (c == '}') { + brace_depth--; + if (brace_depth == 0) break; + } else if (c == '\n') { + yylloc->last_line += 1; + yylloc->last_column = 0; + } + [buf appendFormat:@"%c", c]; + } + + yylval->nsstr = buf; + return DELIMITEDSTRING; +} + +\\begin { return UNKNOWN_BEGIN_CMD; } +\\end { return UNKNOWN_END_CMD; } +\\[a-zA-Z0-9]+ { return UNKNOWN_CMD; } +[a-zA-Z0-9]+ { return UNKNOWN_STR; } +<INITIAL,xcoord,ycoord,props,noderef>. { return UNKNOWN_STR; } + + /* vi:ft=lex:noet:ts=4:sts=4:sw=4: + */ diff --git a/tikzit-1/src/common/tikzparser.ym b/tikzit-1/src/common/tikzparser.ym new file mode 100644 index 0000000..344e969 --- /dev/null +++ b/tikzit-1/src/common/tikzparser.ym @@ -0,0 +1,224 @@ +%{ +/* + * Copyright 2010 Chris Heunen + * Copyright 2010-2013 Aleks Kissinger + * Copyright 2013 K. Johan Paulsson + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "tikzparserdefs.h" +%} + +/* we use features added to bison 2.4 */ +%require "2.3" + +%error-verbose +/* enable maintaining locations for better error messages */ +%locations +/* the name of the header file */ +/*%defines "common/tikzparser.h"*/ +/* make it re-entrant (no global variables) */ +%pure-parser +/* We use a pure (re-entrant) lexer. This means yylex + will take a void* (opaque) type to maintain its state */ +%lex-param {void *scanner} +/* Since this parser is also pure, yyparse needs to take + that lexer state as an argument */ +%parse-param {void *scanner} + +/* possible data types for semantic values */ +%union { + NSString *nsstr; + GraphElementProperty *prop; + GraphElementData *data; + Node *node; + NSPoint pt; + struct noderef noderef; +} + +%{ +#import "GraphElementData.h" +#import "GraphElementProperty.h" +#import "Node.h" +#import "Edge.h" + +#import "tikzlexer.h" +#import "TikzGraphAssembler+Parser.h" +/* the assembler (used by this parser) is stored in the lexer + state as "extra" data */ +#define assembler yyget_extra(scanner) + +/* pass errors off to the assembler */ +void yyerror(YYLTYPE *yylloc, void *scanner, const char *str) { + [assembler reportError:str atLocation:yylloc]; +} +%} + +/* yyloc is set up with first_column = last_column = 1 by default; + however, it makes more sense to think of us being "before the + start of the line" before we parse anything */ +%initial-action { + yylloc.first_column = yylloc.last_column = 0; +} + + +%token BEGIN_TIKZPICTURE_CMD "\\begin{tikzpicture}" +%token END_TIKZPICTURE_CMD "\\end{tikzpicture}" +%token BEGIN_PGFONLAYER_CMD "\\begin{pgfonlayer}" +%token END_PGFONLAYER_CMD "\\end{pgfonlayer}" +%token DRAW_CMD "\\draw" +%token NODE_CMD "\\node" +%token PATH_CMD "\\path" +%token RECTANGLE "rectangle" +%token NODE "node" +%token AT "at" +%token TO "to" +%token SEMICOLON ";" +%token COMMA "," + +%token LEFTPARENTHESIS "(" +%token RIGHTPARENTHESIS ")" +%token LEFTBRACKET "[" +%token RIGHTBRACKET "]" +%token FULLSTOP "." +%token EQUALS "=" +%token <pt> COORD "co-ordinate" +%token <nsstr> PROPSTRING "key/value string" +%token <nsstr> REFSTRING "string" +%token <nsstr> DELIMITEDSTRING "{-delimited string" + +%token UNKNOWN_BEGIN_CMD "unknown \\begin command" +%token UNKNOWN_END_CMD "unknown \\end command" +%token UNKNOWN_CMD "unknown latex command" +%token UNKNOWN_STR "unknown string" +%token UNCLOSED_DELIM_STR "unclosed {-delimited string" + +%type<nsstr> nodename +%type<nsstr> optanchor +%type<nsstr> val +%type<prop> property +%type<data> extraproperties +%type<data> properties +%type<data> optproperties +%type<node> optedgenode +%type<noderef> noderef +%type<noderef> optnoderef + +%% + +tikzpicture: "\\begin{tikzpicture}" optproperties tikzcmds "\\end{tikzpicture}" + { + if ($2) { + [[assembler graph] setData:$2]; + } + }; +tikzcmds: tikzcmds tikzcmd | ; +tikzcmd: node | edge | boundingbox | ignore; + +ignore: "\\begin{pgfonlayer}" DELIMITEDSTRING | "\\end{pgfonlayer}"; + +optproperties: + "[" "]" + { $$ = nil; } + | "[" properties "]" + { $$ = $2; } + | { $$ = nil; }; +properties: extraproperties property + { + [$1 addObject:$2]; + $$ = $1; + }; +extraproperties: + extraproperties property "," + { + [$1 addObject:$2]; + $$ = $1; + } + | { $$ = [GraphElementData data]; }; +property: + val "=" val + { $$ = [GraphElementProperty property:$1 withValue:$3]; } + | val + { $$ = [GraphElementProperty atom:$1]; }; +val: PROPSTRING { $$ = $1; } | DELIMITEDSTRING { $$ = $1; }; + +nodename: "(" REFSTRING ")" { $$ = $2; }; +node: "\\node" optproperties nodename "at" COORD DELIMITEDSTRING ";" + { + Node *node = [[Node alloc] init]; + if ($2) + [node setData:$2]; + [node setName:$3]; + [node setPoint:$5]; + [node setLabel:$6]; + [assembler addNodeToMap:node]; + [[assembler graph] addNode:node]; +#if ! __has_feature(objc_arc) + [node release]; +#endif + }; + +optanchor: { $$ = nil; } | "." REFSTRING { $$ = $2; }; +noderef: "(" REFSTRING optanchor ")" + { + $$.node = [assembler nodeWithName:$2]; + $$.anchor = $3; + }; +optnoderef: + noderef { $$ = $1; } + | "(" ")" { $$.node = nil; $$.anchor = nil; } +optedgenode: + { $$ = nil; } + | "node" optproperties DELIMITEDSTRING + { + $$ = [Node node]; + if ($2) + [$$ setData:$2]; + [$$ setLabel:$3]; + } +edge: "\\draw" optproperties noderef "to" optedgenode optnoderef ";" + { + Edge *edge = [[Edge alloc] init]; + if ($2) + [edge setData:$2]; + [edge setSource:$3.node]; + [edge setSourceAnchor:$3.anchor]; + [edge setEdgeNode:$5]; + if ($6.node) { + [edge setTarget:$6.node]; + [edge setTargetAnchor:$6.anchor]; + } else { + [edge setTarget:$3.node]; + [edge setTargetAnchor:$3.anchor]; + } + [edge setAttributesFromData]; + [[assembler graph] addEdge:edge]; +#if ! __has_feature(objc_arc) + [edge release]; +#endif + }; + +ignoreprop: val | val "=" val; +ignoreprops: ignoreprop ignoreprops | ; +optignoreprops: "[" ignoreprops "]"; +boundingbox: + "\\path" optignoreprops COORD "rectangle" COORD ";" + { + [[assembler graph] setBoundingBox:NSRectAroundPoints($3, $5)]; + }; + +/* vi:ft=yacc:noet:ts=4:sts=4:sw=4 +*/ diff --git a/tikzit-1/src/common/tikzparserdefs.h b/tikzit-1/src/common/tikzparserdefs.h new file mode 100644 index 0000000..cde3345 --- /dev/null +++ b/tikzit-1/src/common/tikzparserdefs.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program 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 2 of + * the License, or (at your option) any later version. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This file sets up some defs (particularly struct noderef) needed for + * the tikz parser and its users. + * + * It is needed because we wish to support bison 2.3, which is the + * version shipped with OSX. bison 2.4 onwards allows us to put this + * stuff in a "%code requires" block, where it will be put in the + * generated header file by bison. + * + * All the types used by the %union directive in tikzparser.ym should + * be declared, defined or imported here. + */ + +// Foundation has NSPoint and NSString +#import <Foundation/Foundation.h> + +@class TikzGraphAssembler; +@class GraphElementData; +@class GraphElementProperty; +@class Node; + +struct noderef { +#if __has_feature(objc_arc) + __unsafe_unretained Node *node; + __unsafe_unretained NSString *anchor; +#else + Node *node; + NSString *anchor; +#endif +}; + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/util.h b/tikzit-1/src/common/util.h new file mode 100644 index 0000000..b34f25d --- /dev/null +++ b/tikzit-1/src/common/util.h @@ -0,0 +1,201 @@ +// +// util.h +// 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. +// + +#import <Foundation/Foundation.h> + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +/*! + @brief Compute a bounding rectangle for two given points. + @param p1 a point. + @param p2 another point. + @result A bounding rectangle for p1 and p2. + */ +NSRect NSRectAroundPoints(NSPoint p1, NSPoint p2); + +/*! + @brief Compute a bounding rectangle for two given points. + @param rect the base rectangle + @param the point to ensure is included + @result A rectangle containing rect and p + */ +NSRect NSRectWithPoint(NSRect rect, NSPoint p); + +/*! + @brief Compute a bounding rectangle for two given points with a given padding. + @param p1 a point. + @param p2 another point. + @param padding a padding. + @result A bounding rectangle for p1 and p2 with padding. + */ +NSRect NSRectAroundPointsWithPadding(NSPoint p1, NSPoint p2, float padding); + +/*! + @brief Compute a bounding rectangle for four given points. + @result A bounding rectangle for p1, p2, p3 and p4. + */ +NSRect NSRectAround4Points(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4); + +/*! + @brief Compute a bounding rectangle for four given points. + @param padding the amount to pad the rectangle + @result A bounding rectangle for p1, p2, p3 and p4 with padding + */ +NSRect NSRectAround4PointsWithPadding(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4, float padding); + +/*! + @brief Find the distance between two points + @param p1 The first point + @param p2 The second point + @result The distance between p1 and p2 + */ +float NSDistanceBetweenPoints(NSPoint p1, NSPoint p2); + +/*! + @brief Compute the 'real' arctan for two points. Always succeeds and gives a good angle, + regardless of sign, zeroes, etc. + @param dx the x distance between points. + @param dy the y distance between points. + @result An angle in radians. + */ +float good_atan(float dx, float dy); + +/*! + @brief Interpolate along a bezier curve to the given distance. To find the x coord, + use the relavant x coordinates for c0-c3, and for y use the y's. + @param dist a distance from 0 to 1 spanning the whole curve. + @param c0 the x (resp. y) coordinate of the start point. + @param c1 the x (resp. y) coordinate of the first control point. + @param c2 the x (resp. y) coordinate of the second control point. + @param c3 the x (resp. y) coordinate of the end point. + @result The x (resp. y) coordinate of the point at 'dist'. + */ +float bezierInterpolate (float dist, float c0, float c1, float c2, float c3); + +/*! + @brief Interpolate along a bezier curve to the given distance. + @param dist a distance from 0 to 1 spanning the whole curve. + @param c0 the x start point. + @param c1 the x first control point. + @param c2 the x second control point. + @param c3 the x end point. + @result The point at 'dist'. + */ +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3); + +/*! + * @brief Find whether two line segments intersect + * @param l1start The starting point of line segment 1 + * @param l1end The ending point of line segment 1 + * @param l2start The starting point of line segment 2 + * @param l2end The ending point of line segment 2 + * @param result A location to store the intersection point + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentsIntersect (NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result); + +/*! + * @brief Find whether a line segment intersects a bezier curve + * @detail Always finds the intersection furthest along the line segment + * @param lstart The starting point of the line segment + * @param lend The ending point of the line segment + * @param c0 The starting point of the bezier curve + * @param c1 The first control point of the bezier curve + * @param c2 The second control point of the bezier curve + * @param c3 The ending point of the bezier curve + * @param result A location to store the intersection point + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); + +/*! + * @brief Find whether a line segment enters a rectangle + * @param lineStart The starting point of the line segment + * @param lineEnd The ending point of the line segment + * @param rect The rectangle + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentIntersectsRect (NSPoint lineStart, NSPoint lineEnd, NSRect rect); + +/*! + * @brief Find where a ray exits a rectangle + * @param rayStart The starting point of the ray; must be contained in rect + * @param angle_rads The angle of the ray, in radians + * @param rect The rectangle + * @result The point at which the ray leaves the rect + */ +NSPoint findExitPointOfRay (NSPoint rayStart, float angle_rads, NSRect rect); + +/*! + @brief Round val to nearest stepSize + @param stepSize the courseness + @param val a value to round + */ +float roundToNearest(float stepSize, float val); + +/*! + @brief Convert radians into degrees + */ +float radiansToDegrees(float radians); + +/*! + @brief Convert degrees into radians + */ +float degreesToRadians(float degrees); + +/*! + @brief Normalises an angle (in degrees) to fall between -179 and 180 + */ +int normaliseAngleDeg (int degrees); + +/*! + @brief Normalises an angle (in radians) to fall in the range (-pi,pi] + */ +float normaliseAngleRad (float rads); + +/*! + @brief Express a byte as alpha-only hex, with digits (0..16) -> (a..jA..F) + @param sh A number 0-255 + @result A string 'aa'-'FF' + */ +NSString *alphaHex(unsigned short sh); + +const char *find_start_of_nth_line (const char * string, int line); + +/*! + @brief Formats a CGFloat as a string, removing trailing zeros + @detail Unlike formatting an NSNumber, or using %g, it will never + produce scientific notation (like "2.00e2"). Unlike %f, + it will not include unnecessary trailing zeros. + */ +NSString *formatFloat(CGFloat f, int maxdps); + diff --git a/tikzit-1/src/common/util.m b/tikzit-1/src/common/util.m new file mode 100644 index 0000000..e9b8899 --- /dev/null +++ b/tikzit-1/src/common/util.m @@ -0,0 +1,403 @@ +// +// util.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. +// + +#import "util.h" +#import "math.h" + +static BOOL fuzzyCompare(float f1, float f2) { + return (ABS(f1 - f2) <= 0.00001f * MIN(ABS(f1), ABS(f2))); +} + +NSRect NSRectWithPoint(NSRect rect, NSPoint p) { + CGFloat minX = NSMinX(rect); + CGFloat maxX = NSMaxX(rect); + CGFloat minY = NSMinY(rect); + CGFloat maxY = NSMaxY(rect); + if (p.x < minX) { + minX = p.x; + } else if (p.x > maxX) { + maxX = p.x; + } + if (p.y < minY) { + minY = p.y; + } else if (p.y > maxY) { + maxY = p.y; + } + return NSMakeRect(minX, minY, maxX - minX, maxY - minY); +} + +NSRect NSRectAroundPointsWithPadding(NSPoint p1, NSPoint p2, float padding) { + return NSMakeRect(MIN(p1.x,p2.x)-padding, + MIN(p1.y,p2.y)-padding, + ABS(p2.x-p1.x)+(2.0f*padding), + ABS(p2.y-p1.y)+(2.0f*padding)); +} + +NSRect NSRectAroundPoints(NSPoint p1, NSPoint p2) { + return NSRectAroundPointsWithPadding(p1, p2, 0.0f); +} + +NSRect NSRectAround4PointsWithPadding(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4, float padding) { + float leftMost = MIN(p1.x, p2.x); + leftMost = MIN(leftMost, p3.x); + leftMost = MIN(leftMost, p4.x); + float rightMost = MAX(p1.x, p2.x); + rightMost = MAX(rightMost, p3.x); + rightMost = MAX(rightMost, p4.x); + float topMost = MIN(p1.y, p2.y); + topMost = MIN(topMost, p3.y); + topMost = MIN(topMost, p4.y); + float bottomMost = MAX(p1.y, p2.y); + bottomMost = MAX(bottomMost, p3.y); + bottomMost = MAX(bottomMost, p4.y); + return NSMakeRect(leftMost-padding, + topMost-padding, + (rightMost - leftMost)+(2.0f*padding), + (bottomMost - topMost)+(2.0f*padding)); +} + +NSRect NSRectAround4Points(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4) { + return NSRectAround4PointsWithPadding(p1, p2, p3, p4, 0.0f); +} + +float NSDistanceBetweenPoints(NSPoint p1, NSPoint p2) { + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + return sqrt(dx * dx + dy * dy); +} + +float good_atan(float dx, float dy) { + if (dx > 0) { + return atan(dy/dx); + } else if (dx < 0) { + return M_PI + atan(dy/dx); + } else { + if (dy > 0) return 0.5 * M_PI; + else if (dy < 0) return 1.5 * M_PI; + else return 0; + } +} + +// interpolate on a cubic bezier curve +float bezierInterpolate(float dist, float c0, float c1, float c2, float c3) { + float distp = 1 - dist; + return (distp*distp*distp) * c0 + + 3 * (distp*distp) * dist * c1 + + 3 * (dist*dist) * distp * c2 + + (dist*dist*dist) * c3; +} + +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3) { + return NSMakePoint (bezierInterpolate (dist, c0.x, c1.x, c2.x, c3.x), + bezierInterpolate (dist, c0.y, c1.y, c2.y, c3.y)); +} + +static void lineCoeffsFromPoints(NSPoint p1, NSPoint p2, float *A, float *B, float *C) { + *A = p2.y - p1.y; + *B = p1.x - p2.x; + *C = (*A) * p1.x + (*B) * p1.y; +} + +static void lineCoeffsFromPointAndAngle(NSPoint p, float angle, float *A, float *B, float *C) { + *A = sin (angle); + *B = -cos (angle); + *C = (*A) * p.x + (*B) * p.y; +} + +static BOOL lineSegmentContainsPoint(NSPoint l1, NSPoint l2, float x, float y) { + float minX = MIN(l1.x, l2.x); + float maxX = MAX(l1.x, l2.x); + float minY = MIN(l1.y, l2.y); + float maxY = MAX(l1.y, l2.y); + return (x >= minX || fuzzyCompare (x, minX)) && + (x <= maxX || fuzzyCompare (x, maxX)) && + (y >= minY || fuzzyCompare (y, minY)) && + (y <= maxY || fuzzyCompare (y, maxY)); +} + +BOOL lineSegmentsIntersect(NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result) { + // Ax + By = C + float A1, B1, C1; + lineCoeffsFromPoints(l1start, l1end, &A1, &B1, &C1); + float A2, B2, C2; + lineCoeffsFromPoints(l2start, l2end, &A2, &B2, &C2); + + float det = A1*B2 - A2*B1; + if (det == 0.0f) { + // parallel + return NO; + } else { + float x = (B2*C1 - B1*C2)/det; + float y = (A1*C2 - A2*C1)/det; + + if (lineSegmentContainsPoint(l1start, l1end, x, y) && + lineSegmentContainsPoint(l2start, l2end, x, y)) { + if (result) { + (*result).x = x; + (*result).y = y; + } + return YES; + } + } + return NO; +} + +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result) { + NSRect curveBounds = NSRectAround4Points(c0, c1, c2, c3); + if (!lineSegmentIntersectsRect(lstart, lend, curveBounds)) + return NO; + + const int divisions = 20; + const float chunkSize = 1.0f/(float)divisions; + float chunkStart = 0.0f; + BOOL found = NO; + + for (int i = 0; i < divisions; ++i) { + float chunkEnd = chunkStart + chunkSize; + + NSPoint p1 = bezierInterpolateFull (chunkStart, c0, c1, c2, c3); + NSPoint p2 = bezierInterpolateFull (chunkEnd, c0, c1, c2, c3); + + NSPoint p; + if (lineSegmentsIntersect (lstart, lend, p1, p2, &p)) { + lstart = p; + found = YES; + } + + chunkStart = chunkEnd; + } + if (found && result) { + *result = lstart; + } + return found; +} + +BOOL lineSegmentIntersectsRect(NSPoint lineStart, NSPoint lineEnd, NSRect rect) { + const float rectMaxX = NSMaxX(rect); + const float rectMinX = NSMinX(rect); + const float rectMaxY = NSMaxY(rect); + const float rectMinY = NSMinY(rect); + + // check if the segment is entirely to one side of the rect + if (lineStart.x > rectMaxX && lineEnd.x > rectMaxX) { + return NO; + } + if (lineStart.x < rectMinX && lineEnd.x < rectMinX) { + return NO; + } + if (lineStart.y > rectMaxY && lineEnd.y > rectMaxY) { + return NO; + } + if (lineStart.y < rectMinY && lineEnd.y < rectMinY) { + return NO; + } + + // Now check whether the (infinite) line intersects the rect + // (if it does, so does the segment, due to above checks) + + // Ax + By = C + float A, B, C; + lineCoeffsFromPoints(lineStart, lineEnd, &A, &B, &C); + + const float tlVal = A * rectMinX + B * rectMaxY - C; + const float trVal = A * rectMaxX + B * rectMaxY - C; + const float blVal = A * rectMinX + B * rectMinY - C; + const float brVal = A * rectMaxX + B * rectMinY - C; + + if (tlVal < 0 && trVal < 0 && blVal < 0 && brVal < 0) { + // rect below line + return NO; + } + if (tlVal > 0 && trVal > 0 && blVal > 0 && brVal > 0) { + // rect above line + return NO; + } + + return YES; +} + +NSPoint findExitPointOfRay (NSPoint p, float angle_rads, NSRect rect) { + const float rectMinX = NSMinX (rect); + const float rectMaxX = NSMaxX (rect); + const float rectMinY = NSMinY (rect); + const float rectMaxY = NSMaxY (rect); + + const float angle = normaliseAngleRad (angle_rads); + + // special case the edges + if (p.y == rectMaxY && angle > 0 && angle < M_PI) { + // along the top of the box + return p; + } + if (p.y == rectMinY && angle < 0 && angle > -M_PI) { + // along the bottom of the box + return p; + } + if (p.x == rectMaxX && angle > -M_PI/2.0f && angle < M_PI/2.0f) { + // along the right of the box + return p; + } + if (p.x == rectMinX && (angle > M_PI/2.0f || angle < -M_PI/2.0f)) { + // along the left of the box + return p; + } + + float A1, B1, C1; + lineCoeffsFromPointAndAngle(p, angle, &A1, &B1, &C1); + //NSLog(@"Ray is %fx + %fy = %f", A1, B1, C1); + + const float tlAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMaxY - p.y)); + const float trAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMaxY - p.y)); + if (angle <= tlAngle && angle >= trAngle) { + // exit top + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMaxX, rectMaxY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float brAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMinY - p.y)); + if (angle <= trAngle && angle >= brAngle) { + // exit right + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMaxY), + NSMakePoint (rectMaxX, rectMinY), + &A2, &B2, &C2); + //NSLog(@"Edge is %fx + %fy = %f", A2, B2, C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float blAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMinY - p.y)); + if (angle <= brAngle && angle >= blAngle) { + // exit bottom + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMinY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } else { + // exit left + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } +} + +float roundToNearest(float stepSize, float val) { + if (stepSize==0.0f) return val; + else return round(val/stepSize)*stepSize; +} + +float radiansToDegrees (float radians) { + return (radians * 180.0f) / M_PI; +} + +float degreesToRadians(float degrees) { + return (degrees * M_PI) / 180.0f; +} + +int normaliseAngleDeg (int degrees) { + while (degrees > 180) { + degrees -= 360; + } + while (degrees <= -180) { + degrees += 360; + } + return degrees; +} + +float normaliseAngleRad (float rads) { + while (rads > M_PI) { + rads -= 2 * M_PI; + } + while (rads <= -M_PI) { + rads += 2 * M_PI; + } + return rads; +} + +static char ahex[] = +{'a','b','c','d','e','f','g','h','i','j', + 'A','B','C','D','E','F'}; + +NSString *alphaHex(unsigned short sh) { + if (sh > 255) return @"!!"; + return [NSString stringWithFormat:@"%c%c", ahex[sh/16], ahex[sh%16]]; +} + +const char *find_start_of_nth_line (const char * string, int line) { + int l = 0; + const char *lineStart = string; + while (*lineStart && l < line) { + while (*lineStart && *lineStart != '\n') { + ++lineStart; + } + if (*lineStart) { + ++l; + ++lineStart; + } + } + return lineStart; +} + +NSString *formatFloat(CGFloat f, int maxdps) { + NSMutableString *result = [NSMutableString + stringWithFormat:@"%.*f", maxdps, f]; + // delete trailing zeros + NSUInteger lastPos = [result length] - 1; + NSUInteger firstDigit = ([result characterAtIndex:0] == '-') ? 1 : 0; + while (lastPos > firstDigit) { + if ([result characterAtIndex:lastPos] == '0') { + [result deleteCharactersInRange:NSMakeRange(lastPos, 1)]; + lastPos -= 1; + } else { + break; + } + } + if ([result characterAtIndex:lastPos] == '.') { + [result deleteCharactersInRange:NSMakeRange(lastPos, 1)]; + lastPos -= 1; + } + if ([@"-0" isEqualToString:result]) + return @"0"; + else + return result; +} + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 |