summaryrefslogtreecommitdiff
path: root/tikzit/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'tikzit/src/common')
-rw-r--r--tikzit/src/common/BasicMapTable.h50
-rw-r--r--tikzit/src/common/BasicMapTable.m75
-rw-r--r--tikzit/src/common/CircleShape.h33
-rw-r--r--tikzit/src/common/CircleShape.m55
-rw-r--r--tikzit/src/common/ColorRGB.h64
-rw-r--r--tikzit/src/common/ColorRGB.m326
-rw-r--r--tikzit/src/common/Edge.h331
-rw-r--r--tikzit/src/common/Edge.m525
-rw-r--r--tikzit/src/common/EdgeStyle.h60
-rw-r--r--tikzit/src/common/EdgeStyle.m114
-rw-r--r--tikzit/src/common/Graph.h279
-rw-r--r--tikzit/src/common/Graph.m599
-rw-r--r--tikzit/src/common/GraphChange.h266
-rw-r--r--tikzit/src/common/GraphChange.m324
-rw-r--r--tikzit/src/common/GraphElementData.h94
-rw-r--r--tikzit/src/common/GraphElementData.m141
-rw-r--r--tikzit/src/common/GraphElementProperty.h91
-rw-r--r--tikzit/src/common/GraphElementProperty.m127
-rw-r--r--tikzit/src/common/Grid.h110
-rw-r--r--tikzit/src/common/Grid.m179
-rw-r--r--tikzit/src/common/NSError+Tikzit.h43
-rw-r--r--tikzit/src/common/NSError+Tikzit.m64
-rw-r--r--tikzit/src/common/NSFileManager+Utils.h29
-rw-r--r--tikzit/src/common/NSFileManager+Utils.m46
-rw-r--r--tikzit/src/common/NSString+LatexConstants.h33
-rw-r--r--tikzit/src/common/NSString+LatexConstants.m204
-rw-r--r--tikzit/src/common/Node.h134
-rw-r--r--tikzit/src/common/Node.m173
-rw-r--r--tikzit/src/common/NodeStyle.h118
-rw-r--r--tikzit/src/common/NodeStyle.m176
-rw-r--r--tikzit/src/common/PickSupport.h152
-rw-r--r--tikzit/src/common/PickSupport.m171
-rw-r--r--tikzit/src/common/Preambles.h66
-rw-r--r--tikzit/src/common/Preambles.m240
-rw-r--r--tikzit/src/common/PropertyHolder.h36
-rw-r--r--tikzit/src/common/PropertyHolder.m68
-rw-r--r--tikzit/src/common/RColor.h50
-rw-r--r--tikzit/src/common/RColor.m33
-rw-r--r--tikzit/src/common/RectangleShape.h33
-rw-r--r--tikzit/src/common/RectangleShape.m54
-rw-r--r--tikzit/src/common/RegularPolyShape.h34
-rw-r--r--tikzit/src/common/RegularPolyShape.m70
-rw-r--r--tikzit/src/common/RenderContext.h156
-rw-r--r--tikzit/src/common/Shape.h42
-rw-r--r--tikzit/src/common/Shape.m141
-rw-r--r--tikzit/src/common/ShapeNames.h27
-rw-r--r--tikzit/src/common/StyleManager.h51
-rw-r--r--tikzit/src/common/StyleManager.m339
-rw-r--r--tikzit/src/common/SupportDir.h36
-rw-r--r--tikzit/src/common/SupportDir.m65
-rw-r--r--tikzit/src/common/TikzGraphAssembler.h58
-rw-r--r--tikzit/src/common/TikzGraphAssembler.m169
-rw-r--r--tikzit/src/common/TikzShape.h34
-rw-r--r--tikzit/src/common/TikzShape.m87
-rw-r--r--tikzit/src/common/Transformer.h154
-rw-r--r--tikzit/src/common/Transformer.m221
-rw-r--r--tikzit/src/common/test/color.m76
-rw-r--r--tikzit/src/common/test/common.m32
-rw-r--r--tikzit/src/common/test/parser.m78
-rw-r--r--tikzit/src/common/test/test.h41
-rw-r--r--tikzit/src/common/test/test.m104
-rw-r--r--tikzit/src/common/tikzlexer.lm101
-rw-r--r--tikzit/src/common/tikzparser.ym178
-rw-r--r--tikzit/src/common/util.h113
-rw-r--r--tikzit/src/common/util.m113
65 files changed, 8286 insertions, 0 deletions
diff --git a/tikzit/src/common/BasicMapTable.h b/tikzit/src/common/BasicMapTable.h
new file mode 100644
index 0000000..e3ddeea
--- /dev/null
+++ b/tikzit/src/common/BasicMapTable.h
@@ -0,0 +1,50 @@
+//
+// BasicMapTable.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 BasicMapTable
+ @brief A stripped-down wrapper for NSMapTable.
+ @details A stripped-down wrapper for NSMapTable. In OS X, this is
+ just an interface to NSMapTable.
+ */
+@interface BasicMapTable : NSObject {
+ NSMapTable *mapTable;
+}
+
+- (id)init;
++ (BasicMapTable*)mapTable;
+- (id)objectForKey:(id)aKey;
+- (void)setObject:(id)anObject forKey:(id)aKey;
+- (NSEnumerator*)objectEnumerator;
+- (NSEnumerator*)keyEnumerator;
+- (NSUInteger)count;
+
+// for fast enumeration
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id *)stackbuf
+ count:(NSUInteger)len;
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/BasicMapTable.m b/tikzit/src/common/BasicMapTable.m
new file mode 100644
index 0000000..163cf8f
--- /dev/null
+++ b/tikzit/src/common/BasicMapTable.m
@@ -0,0 +1,75 @@
+//
+// BasicMapTable.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 "BasicMapTable.h"
+
+@implementation BasicMapTable
+
+- (id)init {
+ [super init];
+
+ mapTable = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory
+ valueOptions:NSMapTableStrongMemory
+ capacity:10];
+
+ return self;
+}
+
++ (BasicMapTable*)mapTable {
+ return [[[BasicMapTable alloc] init] autorelease];
+}
+
+- (id)objectForKey:(id)aKey {
+ return [mapTable objectForKey:aKey];
+}
+
+- (void)setObject:(id)anObject forKey:(id)aKey {
+ [mapTable setObject:anObject forKey:aKey];
+}
+
+- (NSEnumerator*)objectEnumerator {
+ return [mapTable objectEnumerator];
+}
+
+- (NSEnumerator*)keyEnumerator {
+ return [mapTable keyEnumerator];
+}
+
+- (void)dealloc {
+ [mapTable release];
+ [super dealloc];
+}
+
+- (NSUInteger)count {
+ return [mapTable count];
+}
+
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id *)stackbuf
+ count:(NSUInteger)len {
+ return [mapTable countByEnumeratingWithState:state objects:stackbuf count:len];
+}
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/CircleShape.h b/tikzit/src/common/CircleShape.h
new file mode 100644
index 0000000..8215b92
--- /dev/null
+++ b/tikzit/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/src/common/CircleShape.m b/tikzit/src/common/CircleShape.m
new file mode 100644
index 0000000..b154e8f
--- /dev/null
+++ b/tikzit/src/common/CircleShape.m
@@ -0,0 +1,55 @@
+//
+// 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 {
+ [super init];
+
+ 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];
+
+ return self;
+}
+
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/ColorRGB.h b/tikzit/src/common/ColorRGB.h
new file mode 100644
index 0000000..1c9b27d
--- /dev/null
+++ b/tikzit/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 {
+ 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;
+
+- (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;
+
+- (NSString*)name;
+
+- (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/src/common/ColorRGB.m b/tikzit/src/common/ColorRGB.m
new file mode 100644
index 0000000..7b3e51f
--- /dev/null
+++ b/tikzit/src/common/ColorRGB.m
@@ -0,0 +1,326 @@
+//
+// 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 "BasicMapTable.h"
+#import "util.h"
+
+typedef struct {
+ NSString *name;
+ 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 BasicMapTable *colorHash = nil;
+
+@implementation ColorRGB
+
+- (unsigned short)red { return red; }
+- (void)setRed:(unsigned short)r { red = r; }
+- (unsigned short)green { return green; }
+- (void)setGreen:(unsigned short)g { green = g; }
+- (unsigned short)blue { return blue; }
+- (void)setBlue:(unsigned short)b { blue = b; }
+
+- (float)redFloat { return ((float)red)/255.0f; }
+- (void)setRedFloat:(float)r { red = round(r*255.0f); }
+- (float)greenFloat { return ((float)green)/255.0f; }
+- (void)setGreenFloat:(float)g { green = round(g*255.0f); }
+- (float)blueFloat { return ((float)blue)/255.0f; }
+- (void)setBlueFloat:(float)b { blue = 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 {
+ [super init];
+ red = r;
+ green = g;
+ blue = b;
+ return self;
+}
+
+- (id)initWithFloatRed:(float)r green:(float)g blue:(float)b {
+ [super init];
+ 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)copy {
+ ColorRGB *col = [[ColorRGB alloc] 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];
+ return [col autorelease];
+}
+
+
++ (ColorRGB*)colorWithFloatRed:(float)r green:(float)g blue:(float)b {
+ ColorRGB *col = [[ColorRGB alloc] initWithFloatRed:r green:g blue:b];
+ return [col autorelease];
+}
+
++ (ColorRGB*) colorWithRColor:(RColor)color {
+ return [[[self alloc] initWithRColor:color] autorelease];
+}
+
++ (void)makeColorHash {
+ BasicMapTable *h = [[BasicMapTable 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]);
+ [col release];
+ }
+ colorHash = h;
+}
+
++ (void)releaseColorHash {
+ [colorHash release];
+}
+
+- (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/src/common/Edge.h b/tikzit/src/common/Edge.h
new file mode 100644
index 0000000..460ecc4
--- /dev/null
+++ b/tikzit/src/common/Edge.h
@@ -0,0 +1,331 @@
+//
+// 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 {
+ Node *source;
+ Node *target;
+ Node *edgeNode;
+ int bend;
+ int inAngle, outAngle;
+ EdgeBendMode bendMode;
+ float weight;
+ EdgeStyle *style;
+ GraphElementData *data;
+
+ // 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 cp1;
+ NSPoint cp2;
+ NSPoint mid;
+ NSPoint midTan;
+}
+
+/*!
+ @property data
+ @brief Associated edge data.
+ */
+@property (copy) GraphElementData *data;
+
+/*!
+ @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 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 cp1
+ @brief The first control point. Computed from the source, target, and bend.
+ */
+@property (readonly) NSPoint cp1;
+
+/*!
+ @property cp2
+ @brief The second control point. Computed from the source, target, and bend.
+ */
+@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 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 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 Copy this edge.
+ @result A copy of this edge.
+ */
+- (id)copy;
+
+/*!
+ @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/src/common/Edge.m b/tikzit/src/common/Edge.m
new file mode 100644
index 0000000..c89be6d
--- /dev/null
+++ b/tikzit/src/common/Edge.m
@@ -0,0 +1,525 @@
+//
+// Edge.m
+// TikZiT
+//
+// Copyright 2010 Aleks Kissinger. All rights reserved.
+//
+//
+// This file is part of TikZiT.
+//
+// TikZiT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TikZiT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TikZiT. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#import "Edge.h"
+#import "util.h"
+
+@implementation Edge
+
+- (id)init {
+ [super init];
+ data = [[GraphElementData alloc] init];
+ bend = 0;
+ inAngle = 135;
+ outAngle = 45;
+ bendMode = EdgeBendModeBasic;
+ weight = 0.4f;
+ dirty = YES;
+ source = nil;
+ target = nil;
+ edgeNode = nil;
+
+ return self;
+}
+
+- (id)initWithSource:(Node*)s andTarget:(Node*)t {
+ [self init];
+
+ [self setSource:s];
+ [self setTarget:t];
+ edgeNode = nil;
+
+ dirty = YES;
+
+ return self;
+}
+
+- (BOOL)attachStyleFromTable:(NSArray*)styles {
+ NSString *style_name = [data propertyForKey:@"style"];
+
+ [self setStyle:nil];
+ if (style_name == nil) return YES;
+
+ for (EdgeStyle *s in styles) {
+ if ([[s name] compare:style_name]==NSOrderedSame) {
+ [self setStyle:s];
+ return YES;
+ }
+ }
+
+ // if we didn't find a style, fill in a default one
+ style = [[EdgeStyle defaultEdgeStyleWithName:style_name] retain];
+ return NO;
+}
+
+- (void)updateControls {
+ // check for external modification to the node locations
+ if (src.x != [source point].x || src.y != [source point].y ||
+ targ.x != [target point].x || targ.y != [target point].y)
+ {
+ dirty = YES;
+ }
+
+ if (dirty) {
+ src = [source point];
+ targ = [target point];
+
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+
+ float angleSrc = 0.0f;
+ float angleTarg = 0.0f;
+
+ bend = normaliseAngleDeg (bend);
+ inAngle = normaliseAngleDeg (inAngle);
+ outAngle = normaliseAngleDeg (outAngle);
+ if (bendMode == EdgeBendModeBasic) {
+ float angle = good_atan(dx, dy);
+ float bnd = (float)bend * (M_PI / 180.0f);
+ angleSrc = angle - bnd;
+ angleTarg = M_PI + angle + bnd;
+ } else if (bendMode == EdgeBendModeInOut) {
+ angleSrc = (float)outAngle * (M_PI / 180.0f);
+ angleTarg = (float)inAngle * (M_PI / 180.0f);
+ }
+
+ // give a default distance for self-loops
+ float cdist = (dx==0.0f && dy==0.0f) ? weight : sqrt(dx*dx + dy*dy) * weight;
+
+ cp1 = NSMakePoint(src.x + (cdist * cos(angleSrc)),
+ src.y + (cdist * sin(angleSrc)));
+
+ cp2 = NSMakePoint(targ.x + (cdist * cos(angleTarg)),
+ targ.y + (cdist * sin(angleTarg)));
+
+ mid.x = bezierInterpolate(0.5f, src.x, cp1.x, cp2.x, targ.x);
+ mid.y = bezierInterpolate(0.5f, src.y, cp1.y, cp2.y, targ.y);
+
+ dx = bezierInterpolate(0.6f, src.x, cp1.x, cp2.x, targ.x) -
+ bezierInterpolate(0.4f, src.x, cp1.x, cp2.x, targ.x);
+ dy = bezierInterpolate(0.6f, src.y, cp1.y, cp2.y, targ.y) -
+ bezierInterpolate(0.4f, src.y, cp1.y, cp2.y, targ.y);
+
+ // normalise
+ float len = sqrt(dx*dx+dy*dy);
+ if (len != 0) {
+ dx = (dx/len) * 0.1f;
+ dy = (dy/len) * 0.1f;
+ }
+
+ midTan.x = mid.x + dx;
+ midTan.y = mid.y + dy;
+ }
+ dirty = NO;
+}
+
+- (void)convertBendToAngles {
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+ float angle = good_atan(dx, dy);
+ float bnd = (float)bend * (M_PI / 180.0f);
+
+ outAngle = round((angle - bnd) * (180.0f/M_PI));
+ inAngle = round((M_PI + angle + bnd) * (180.0f/M_PI));
+ dirty = YES;
+}
+
+- (void)convertAnglesToBend {
+ float dx = (targ.x - src.x);
+ float dy = (targ.y - src.y);
+ int angle = round((180.0f/M_PI) * good_atan(dx, dy));
+
+ // compute bend1 and bend2 to match inAngle and outAngle, resp.
+ int bend1, bend2;
+
+ bend1 = outAngle - angle;
+ bend2 = angle - inAngle;
+
+ bend = (bend1 + bend2) / 2;
+
+ dirty = YES;
+}
+
+- (BOOL)isSelfLoop {
+ return (source == target);
+}
+
+- (BOOL)isStraight {
+ return (bendMode == EdgeBendModeBasic && bend == 0);
+}
+
+- (NSPoint)mid {
+ [self updateControls];
+ return mid;
+}
+
+- (NSPoint)midTan {
+ [self updateControls];
+ return midTan;
+}
+
+- (NSPoint)leftNormal {
+ [self updateControls];
+ return NSMakePoint(mid.x + (mid.y - midTan.y), mid.y - (mid.x - midTan.x));
+}
+
+- (NSPoint)rightNormal {
+ [self updateControls];
+ return NSMakePoint(mid.x - (mid.y - midTan.y), mid.y + (mid.x - midTan.x));
+}
+
+- (NSPoint)cp1 {
+ [self updateControls];
+ return cp1;
+}
+
+- (NSPoint)cp2 {
+ [self updateControls];
+ return cp2;
+}
+
+- (int)inAngle {return inAngle;}
+- (void)setInAngle:(int)a {
+ inAngle = a;
+ dirty = YES;
+}
+
+- (int)outAngle {return outAngle;}
+- (void)setOutAngle:(int)a {
+ outAngle = a;
+ dirty = YES;
+}
+
+- (EdgeBendMode)bendMode {return bendMode;}
+- (void)setBendMode:(EdgeBendMode)mode {
+ bendMode = mode;
+ dirty = YES;
+}
+
+- (int)bend {return bend;}
+- (void)setBend:(int)b {
+ bend = b;
+ dirty = YES;
+}
+
+- (float)weight {return weight;}
+- (void)setWeight:(float)w {
+// if (source == target) weight = 1.0f;
+// else weight = w;
+ weight = w;
+ dirty = YES;
+}
+
+- (EdgeStyle*)style {return style;}
+- (void)setStyle:(EdgeStyle*)s {
+ if (style != s) {
+ [style release];
+ style = [s retain];
+ }
+}
+
+- (Node*)source {return source;}
+- (void)setSource:(Node *)s {
+ if (source != s) {
+ [source release];
+ source = [s retain];
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ dirty = YES;
+ }
+}
+
+- (Node*)target {return target;}
+- (void)setTarget:(Node *)t {
+ if (target != t) {
+ [target release];
+ target = [t retain];
+
+ if (source==target) {
+ bendMode = EdgeBendModeInOut;
+ weight = 1.0f;
+ }
+
+ dirty = YES;
+ }
+}
+
+
+// edgeNode and hasEdgeNode use a bit of key-value observing to help the mac GUI keep up
+
+- (Node*)edgeNode {return edgeNode;}
+- (void)setEdgeNode:(Node *)n {
+#if defined (__APPLE__)
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+#endif
+ if (edgeNode != n) {
+ hasEdgeNode = (n != nil);
+ [edgeNode release];
+ edgeNode = [n retain];
+ // don't set dirty bit, because control points don't need update
+ }
+#if defined (__APPLE__)
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+#endif
+}
+
+- (BOOL)hasEdgeNode { return hasEdgeNode; }
+- (void)setHasEdgeNode:(BOOL)b {
+#if defined (__APPLE__)
+ [self willChangeValueForKey:@"edgeNode"];
+ [self willChangeValueForKey:@"hasEdgeNode"];
+#endif
+ hasEdgeNode = b;
+ if (hasEdgeNode && edgeNode == nil) {
+ edgeNode = [[Node alloc] init];
+ }
+#if defined (__APPLE__)
+ [self didChangeValueForKey:@"edgeNode"];
+ [self didChangeValueForKey:@"hasEdgeNode"];
+#endif
+}
+
+- (GraphElementData*)data {return data;}
+- (void)setData:(GraphElementData*)dt {
+ if (data != dt) {
+ [data release];
+ data = [dt copy];
+ }
+}
+
+- (void)updateData {
+ // unset everything to avoid redundant defs
+ [data unsetAtom:@"loop"];
+ [data unsetProperty:@"in"];
+ [data unsetProperty:@"out"];
+ [data unsetAtom:@"bend left"];
+ [data unsetAtom:@"bend right"];
+ [data unsetProperty:@"bend left"];
+ [data unsetProperty:@"bend right"];
+ [data unsetProperty:@"looseness"];
+
+ if (style == nil) {
+ [data unsetProperty:@"style"];
+ } else {
+ [data setProperty:[style name] forKey:@"style"];
+ }
+
+ if (bendMode == EdgeBendModeBasic && bend != 0) {
+ NSString *bendkey = @"bend right";
+ int b = [self bend];
+ if (b < 0) {
+ bendkey = @"bend left";
+ b = -b;
+ }
+
+ if (b == 30) {
+ [data setAtom:bendkey];
+ } else {
+ [data setProperty:[NSString stringWithFormat:@"%d",b] forKey:bendkey];
+ }
+
+ } else if (bendMode == EdgeBendModeInOut) {
+ [data setProperty:[NSString stringWithFormat:@"%d",inAngle]
+ forKey:@"in"];
+ [data setProperty:[NSString stringWithFormat:@"%d",outAngle]
+ forKey:@"out"];
+ }
+
+ // loop needs to come after in/out
+ if (source == target) [data setAtom:@"loop"];
+
+ if (!(
+ ([self isSelfLoop]) ||
+ (source!=target && weight == 0.4f)
+ ))
+ {
+ [data setProperty:[NSString stringWithFormat:@"%.2f",weight*2.5f]
+ forKey:@"looseness"];
+ }
+}
+
+- (void)setAttributesFromData {
+ bendMode = EdgeBendModeBasic;
+
+ if ([data isAtomSet:@"bend left"]) {
+ bend = -30;
+ } else if ([data isAtomSet:@"bend right"]) {
+ bend = 30;
+ } else if ([data propertyForKey:@"bend left"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend left"];
+ bend = -[bnd intValue];
+ } else if ([data propertyForKey:@"bend right"] != nil) {
+ NSString *bnd = [data propertyForKey:@"bend right"];
+ bend = [bnd intValue];
+ } else {
+ bend = 0;
+
+ if ([data propertyForKey:@"in"] != nil && [data propertyForKey:@"out"] != nil) {
+ bendMode = EdgeBendModeInOut;
+ inAngle = [[data propertyForKey:@"in"] intValue];
+ outAngle = [[data propertyForKey:@"out"] intValue];
+ }
+ }
+
+ if ([data propertyForKey:@"looseness"] != nil) {
+ weight = [[data propertyForKey:@"looseness"] floatValue] / 2.5f;
+ } else {
+ weight = ([self isSelfLoop]) ? 1.0f : 0.4f;
+ }
+}
+
+- (void)setPropertiesFromEdge:(Edge*)e {
+ Node *en = [[e edgeNode] copy];
+ [self setEdgeNode:en];
+ [en release];
+
+ GraphElementData *d = [[e data] copy];
+ [self setData:d];
+ [d release];
+
+ [self setStyle:[e style]];
+ [self setBend:[e bend]];
+ [self setInAngle:[e inAngle]];
+ [self setOutAngle:[e outAngle]];
+ [self setBendMode:[e bendMode]];
+ [self setWeight:[e weight]];
+
+ dirty = YES; // cached data will be recomputed lazily, rather than copied
+}
+
+- (NSRect)boundingRect {
+ [self updateControls];
+ return NSRectAround4Points(src, targ, cp1, cp2);
+}
+
+- (void) adjustWeight:(float)handle_dist withCourseness:(float)wcourseness {
+ float base_dist = NSDistanceBetweenPoints (src, targ);
+ if (base_dist == 0.0f) {
+ base_dist = 1.0f;
+ }
+
+ [self setWeight:roundToNearest(wcourseness, handle_dist / base_dist)];
+}
+
+- (float) angleOf:(NSPoint)point relativeTo:(NSPoint)base {
+ float dx = point.x - base.x;
+ float dy = point.y - base.y;
+ return radiansToDegrees (good_atan(dx, dy));
+}
+
+- (void) moveCp1To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link {
+ [self updateControls];
+ [self adjustWeight:NSDistanceBetweenPoints (point, src) withCourseness:wc];
+
+ float control_angle = [self angleOf:point relativeTo:src];
+ if (bendMode == EdgeBendModeBasic) {
+ float base_angle = [self angleOf:targ relativeTo:src];
+ int b = (int)roundToNearest (bc, base_angle - control_angle);
+ [self setBend:b];
+ } else {
+ int angle = (int)roundToNearest (bc, control_angle);
+ if (link) {
+ [self setInAngle:(inAngle + angle - outAngle)];
+ }
+ [self setOutAngle:angle];
+ }
+}
+
+- (void) moveCp1To:(NSPoint)point {
+ [self moveCp1To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO];
+}
+
+- (void) moveCp2To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link {
+ [self updateControls];
+
+ if (![self isSelfLoop]) {
+ [self adjustWeight:NSDistanceBetweenPoints (point, targ) withCourseness:wc];
+ }
+
+ float control_angle = [self angleOf:point relativeTo:targ];
+ if (bendMode == EdgeBendModeBasic) {
+ float base_angle = [self angleOf:src relativeTo:targ];
+ int b = (int)roundToNearest (bc, control_angle - base_angle);
+ [self setBend:b];
+ } else {
+ int angle = (int)roundToNearest (bc, control_angle);
+ if (link) {
+ [self setOutAngle:(outAngle + angle - inAngle)];
+ }
+ [self setInAngle: angle];
+ }
+}
+
+- (void) moveCp2To:(NSPoint)point {
+ [self moveCp2To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO];
+}
+
+- (void)reverse {
+ Node *n;
+ float f;
+
+ n = source;
+ source = target;
+ target = n;
+
+ f = inAngle;
+ inAngle = outAngle;
+ outAngle = f;
+
+ bend = -1 * bend;
+
+ dirty = YES;
+}
+
+- (void)dealloc {
+ [self setSource:nil];
+ [self setTarget:nil];
+ [self setData:nil];
+ [super dealloc];
+}
+
+- (id)copy {
+ Edge *cp = [[Edge alloc] init];
+ [cp setSource:[self source]];
+ [cp setTarget:[self target]];
+ [cp setPropertiesFromEdge:self];
+ return cp;
+}
+
++ (Edge*)edge {
+ return [[[Edge alloc] init] autorelease];
+}
+
++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t {
+ return [[[Edge alloc] initWithSource:s andTarget:t] autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/EdgeStyle.h b/tikzit/src/common/EdgeStyle.h
new file mode 100644
index 0000000..19ca173
--- /dev/null
+++ b/tikzit/src/common/EdgeStyle.h
@@ -0,0 +1,60 @@
+//
+// 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"
+
+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 {
+ ArrowHeadStyle headStyle, tailStyle;
+ EdgeDectorationStyle decorationStyle;
+ float thickness;
+ NSString *name;
+ NSString *category;
+}
+
+@property (copy) NSString *name;
+@property (copy) NSString *category;
+@property (assign) ArrowHeadStyle headStyle;
+@property (assign) ArrowHeadStyle tailStyle;
+@property (assign) EdgeDectorationStyle decorationStyle;
+@property (assign) float thickness;
+
+- (id)init;
+- (id)initWithName:(NSString*)nm;
++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/EdgeStyle.m b/tikzit/src/common/EdgeStyle.m
new file mode 100644
index 0000000..641d898
--- /dev/null
+++ b/tikzit/src/common/EdgeStyle.m
@@ -0,0 +1,114 @@
+//
+// 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
+
+- (id)init {
+ self = [super initWithNotificationName:@"EdgeStylePropertyChanged"];
+
+ if (self != nil) {
+ headStyle = AH_None;
+ tailStyle = AH_None;
+ decorationStyle = ED_None;
+ name = @"new";
+ category = nil;
+ thickness = 1.0f;
+ }
+
+ return self;
+}
+
+- (id)initWithName:(NSString*)nm {
+ self = [self init];
+
+ if (self != nil) {
+ [self setName:nm];
+ }
+
+ return self;
+}
+
++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm {
+ return [[[EdgeStyle alloc] initWithName:nm] autorelease];
+}
+
+- (NSString*)name { return name; }
+- (void)setName:(NSString *)s {
+ if (name != s) {
+ NSString *oldValue = name;
+ name = [s copy];
+ [self postPropertyChanged:@"name" oldValue:oldValue];
+ [oldValue release];
+ }
+}
+
+- (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];
+ [oldValue release];
+ }
+}
+
+- (void)dealloc {
+ [name release];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/Graph.h b/tikzit/src/common/Graph.h
new file mode 100644
index 0000000..fe8c2e5
--- /dev/null
+++ b/tikzit/src/common/Graph.h
@@ -0,0 +1,279 @@
+//
+// 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 "BasicMapTable.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 {
+ NSLock *graphLock;
+ BOOL dirty; // this bit keeps track of whether nodesCache and edgesCache are up to date
+ NSMutableSet *nodes;
+ NSMutableSet *edges;
+ NSSet *nodesCache;
+ NSSet *edgesCache;
+
+ BasicMapTable *inEdges;
+ BasicMapTable *outEdges;
+
+ GraphElementData *data;
+ NSRect boundingBox;
+}
+
+/*!
+ @property data
+ @brief Data associated with the graph.
+ */
+@property (copy) GraphElementData *data;
+
+/*!
+ @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) NSSet *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) NSSet *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 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 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:(NSSet*)ns byPoint:(NSPoint)p;
+
+/*!
+ @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>BasicMapTable</tt> of node copies.
+ */
++ (BasicMapTable*)nodeTableForNodes:(NSSet*)nds;
+
+/*!
+ @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>BasicMapTable</tt> of edge copies.
+ */
++ (BasicMapTable*)edgeTableForEdges:(NSSet*)es;
+
+/*!
+ @brief Compute the bounds for a set of nodes.
+ @param nds a set of nodes.
+ @result The bounds.
+ */
++ (NSRect)boundsForNodeSet:(NSSet *)nds;
+
+/*!
+ @brief Factory method for constructing graphs.
+ @result An empty graph.
+ */
++ (Graph*)graph;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/Graph.m b/tikzit/src/common/Graph.m
new file mode 100644
index 0000000..62774f5
--- /dev/null
+++ b/tikzit/src/common/Graph.m
@@ -0,0 +1,599 @@
+//
+// 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 "BasicMapTable.h"
+
+@implementation Graph
+
+- (Graph*)init {
+ [super init];
+ data = [[GraphElementData alloc] init];
+ boundingBox = NSMakeRect(0, 0, 0, 0);
+ graphLock = [[NSRecursiveLock alloc] init];
+ [graphLock lock];
+ nodes = [[NSMutableSet alloc] initWithCapacity:10];
+ edges = [[NSMutableSet alloc] initWithCapacity:10];
+ inEdges = nil;
+ outEdges = nil;
+ dirty = YES;
+ [graphLock unlock];
+ return self;
+}
+
+- (void)sync {
+ [graphLock lock];
+ if (dirty) {
+ [nodesCache release];
+ nodesCache = [nodes copy];
+ [edgesCache release];
+ edgesCache = [edges copy];
+
+
+ [inEdges release];
+ [outEdges release];
+ inEdges = [[BasicMapTable alloc] init];
+ outEdges = [[BasicMapTable alloc] init];
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ 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];
+ }
+
+ [pool drain];
+
+
+ dirty = NO;
+ }
+ [graphLock unlock];
+}
+
+- (NSSet*)nodes {
+ [self sync];
+ return nodesCache;
+}
+
+- (NSSet*)edges {
+ [self sync];
+ return edgesCache;
+}
+
+- (NSRect)bounds {
+ [graphLock lock];
+ NSRect b = [Graph boundsForNodeSet:nodes];
+ [graphLock unlock];
+ return b;
+}
+
+- (GraphElementData*)data { return data; }
+- (void)setData:(GraphElementData *)dt {
+ if (data != dt) {
+ [data release];
+ data = [dt copy];
+ }
+}
+
+- (NSRect)boundingBox { return boundingBox; }
+- (void)setBoundingBox:(NSRect)r {
+ boundingBox = r;
+}
+
+- (BOOL)hasBoundingBox {
+ return !(
+ boundingBox.size.width == 0 &&
+ boundingBox.size.height == 0
+ );
+}
+
+- (NSSet*)inEdgesForNode:(Node*)nd {
+ return [[[inEdges objectForKey:nd] retain] autorelease];
+}
+
+- (NSSet*)outEdgesForNode:(Node*)nd {
+ return [[[outEdges objectForKey:nd] retain] autorelease];
+}
+
+- (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];
+ [nodes addObject:node];
+ dirty = YES;
+ [graphLock unlock];
+
+ return [GraphChange graphAdditionWithNodes:[NSSet setWithObject:node]
+ 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];
+ [edges addObject:edge];
+ dirty = YES;
+ [graphLock unlock];
+ return [GraphChange graphAdditionWithNodes:[NSSet set]
+ edges:[NSSet setWithObject:edge]];
+}
+
+- (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];
+
+ NSEnumerator *en = [es objectEnumerator];
+ Edge *e;
+ while ((e = [en nextObject])) {
+ [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:(NSSet*)ns byPoint:(NSPoint)p {
+ NSEnumerator *en = [ns objectEnumerator];
+ Node *n;
+ NSPoint newLoc;
+ while ((n = [en nextObject])) {
+ newLoc = NSMakePoint([n point].x + p.x, [n point].y + p.y);
+ [n setPoint:newLoc];
+ }
+ return [GraphChange shiftNodes:ns byPoint:p];
+}
+
+- (GraphChange*)insertGraph:(Graph*)g {
+ [graphLock lock];
+ NSEnumerator *en;
+ Node *n;
+ Edge *e;
+
+ en = [[g nodes] objectEnumerator];
+ while ((n = [en nextObject])) {
+ [nodes addObject:n];
+ }
+
+ en = [[g edges] objectEnumerator];
+ while ((e = [en nextObject])) {
+ [edges addObject:e];
+ }
+
+ dirty = YES;
+
+ [graphLock unlock];
+
+ return [GraphChange graphAdditionWithNodes:[g nodes] edges:[g edges]];
+}
+
+- (void)flipNodes:(NSSet*)nds horizontal:(BOOL)horiz {
+ [graphLock lock];
+
+ NSRect bds = [Graph boundsForNodeSet: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 {
+ [graphLock lock];
+
+ BasicMapTable *newNds = [Graph nodeTableForNodes:nds];
+ Graph* newGraph = [[Graph graph] retain];
+
+ NSEnumerator *en = [newNds objectEnumerator];
+ Node *nd;
+ while ((nd = [en nextObject])) {
+ [newGraph addNode:nd];
+ }
+
+ en = [edges objectEnumerator];
+ Edge *e;
+ while ((e = [en nextObject])) {
+ if ([nds containsObject:[e source]] && [nds containsObject:[e target]]) {
+ Edge *e1 = [e copy];
+ [e1 setSource:[newNds objectForKey:[e source]]];
+ [e1 setTarget:[newNds objectForKey:[e target]]];
+ [newGraph addEdge:e1];
+ [e1 release]; // e1 belongs to newGraph
+ }
+ }
+
+ [graphLock unlock];
+
+ return newGraph;
+}
+
+- (NSSet*)pathCover {
+ [self sync];
+
+ NSMutableSet *remainingEdges = [edges mutableCopy];
+ NSMutableSet *cover = [NSMutableSet set];
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+ 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];
+ [path release];
+ }
+
+ [pool drain];
+ [remainingEdges release];
+ return cover;
+}
+
+- (void)applyGraphChange:(GraphChange*)ch {
+ [graphLock lock];
+ Node *n;
+ Edge *e;
+ NSEnumerator *en;
+ switch ([ch changeType]) {
+ case GraphAddition:
+ en = [[ch affectedNodes] objectEnumerator];
+ while ((n = [en nextObject])) [nodes addObject:n];
+
+ en = [[ch affectedEdges] objectEnumerator];
+ while ((e = [en nextObject])) [edges addObject:e];
+
+ break;
+ case GraphDeletion:
+ en = [[ch affectedEdges] objectEnumerator];
+ while ((e = [en nextObject])) [edges removeObject:e];
+
+ en = [[ch affectedNodes] objectEnumerator];
+ while ((n = [en nextObject])) [nodes removeObject:n];
+
+ break;
+ case NodePropertyChange:
+ [[ch nodeRef] setPropertiesFromNode:[ch nwNode]];
+ break;
+ case NodesPropertyChange:
+ en = [[ch nwNodeTable] keyEnumerator];
+ Node *key;
+ while ((key = [en nextObject])) {
+ [key setPropertiesFromNode:[[ch nwNodeTable] objectForKey:key]];
+ }
+ break;
+ case EdgePropertyChange:
+ [[ch edgeRef] setPropertiesFromEdge:[ch nwEdge]];
+ break;
+ case NodesShift:
+ en = [[ch affectedNodes] objectEnumerator];
+ NSPoint newLoc;
+ while ((n = [en nextObject])) {
+ newLoc = NSMakePoint([n point].x + [ch shiftPoint].x,
+ [n point].y + [ch shiftPoint].y);
+ [n setPoint:newLoc];
+ }
+ break;
+ case NodesFlip:
+ [self flipNodes:[ch affectedNodes] horizontal:[ch horizontal]];
+ break;
+ case BoundingBoxChange:
+ [self setBoundingBox:[ch nwBoundingBox]];
+ break;
+ case GraphPropertyChange:
+ [self setData:[ch nwGraphData]];
+ 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] stringList]];
+
+ 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;
+ for (i = 0; i < [sortedNodeList count]; ++i) {
+ Node *n = [sortedNodeList objectAtIndex:i];
+ [n updateData];
+ [n setName:[NSString stringWithFormat:@"%d", i]];
+ [code appendFormat:@"\t\t\\node %@ (%d) at (%@, %@) {%@};\n",
+ [[n data] stringList],
+ i,
+ [NSNumber numberWithFloat:[n point].x],
+ [NSNumber numberWithFloat:[n point].y],
+ [n label]
+ ];
+ }
+
+ 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] stringList],
+ [[e edgeNode] label]
+ ];
+ } else {
+ nodeStr = @"";
+ }
+
+ NSString *edata = [[e data] stringList];
+
+ [code appendFormat:@"\t\t\\draw%@ (%@%@) to %@(%@%@);\n",
+ ([edata isEqual:@""]) ? @"" : [NSString stringWithFormat:@" %@", edata],
+ [[e source] name],
+ ([[e source] style] == nil) ? @".center" : @"",
+ nodeStr,
+ ([e source] == [e target]) ? @"" : [[e target] name],
+ ([e source] != [e target] && [[e target] style] == nil) ? @".center" : @""
+ ];
+ }
+
+ if ([edges count] > 0) [code appendFormat:@"\t\\end{pgfonlayer}\n"];
+
+ [code appendString:@"\\end{tikzpicture}"];
+
+ [graphLock unlock];
+
+ return code;
+}
+
+- (void)dealloc {
+ [graphLock lock];
+ [nodes release];
+ [edges release];
+ [nodesCache release];
+ [edgesCache release];
+ [data release];
+ [inEdges release];
+ [outEdges release];
+ [graphLock unlock];
+ [graphLock release];
+
+ [super dealloc];
+}
+
++ (Graph*)graph {
+ return [[[self alloc] init] autorelease];
+}
+
++ (BasicMapTable*)nodeTableForNodes:(NSSet*)nds {
+ BasicMapTable *tab = [BasicMapTable mapTable];
+ for (Node *n in nds) {
+ Node *ncopy = [n copy];
+ [tab setObject:ncopy forKey:n];
+ [ncopy release]; // tab should still retain ncopy.
+ }
+ return tab;
+}
+
++ (BasicMapTable*)edgeTableForEdges:(NSSet*)es {
+ BasicMapTable *tab = [BasicMapTable mapTable];
+ for (Edge *e in es) {
+ Edge *ecopy = [e copy];
+ [tab setObject:ecopy forKey:e];
+ [ecopy release]; // tab should still retain ecopy.
+ }
+ return tab;
+}
+
++ (NSRect)boundsForNodeSet:(NSSet*)nds {
+ NSPoint tl, br;
+ Node *n;
+ NSPoint p;
+ NSEnumerator *en = [nds objectEnumerator];
+ if ((n = [en nextObject])==nil) {
+ return NSMakeRect(0, 0, 0, 0);
+ }
+ p = [n point];
+ tl = p;
+ br = p;
+ while ((n = [en nextObject])) {
+ p = [n point];
+ 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 NSRectAroundPoints(tl, br);
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/GraphChange.h b/tikzit/src/common/GraphChange.h
new file mode 100644
index 0000000..0ad0c97
--- /dev/null
+++ b/tikzit/src/common/GraphChange.h
@@ -0,0 +1,266 @@
+//
+// 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"
+#import "BasicMapTable.h"
+
+typedef enum {
+ GraphAddition,
+ GraphDeletion,
+ NodePropertyChange,
+ EdgePropertyChange,
+ NodesPropertyChange,
+ NodesShift,
+ NodesFlip,
+ BoundingBoxChange,
+ GraphPropertyChange
+} 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;
+ BasicMapTable *oldNodeTable, *nwNodeTable;
+ NSRect oldBoundingBox, nwBoundingBox;
+ GraphElementData *oldGraphData, *nwGraphData;
+}
+
+/*!
+ @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) BasicMapTable *oldNodeTable;
+
+/*!
+ @property nwNodeTable
+ @brief A a table containing copies of a set of nodes post-change.
+ */
+@property (retain) BasicMapTable *nwNodeTable;
+
+/*!
+ @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;
+
+/*!
+ @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:(BasicMapTable*)oldC
+ toNewCopies:(BasicMapTable*)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 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;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/GraphChange.m b/tikzit/src/common/GraphChange.m
new file mode 100644
index 0000000..d09e732
--- /dev/null
+++ b/tikzit/src/common/GraphChange.m
@@ -0,0 +1,324 @@
+//
+// 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 {
+ [super init];
+ return self;
+}
+
+- (ChangeType)changeType { return changeType; }
+
+- (void)setChangeType:(ChangeType)ct {
+ changeType = ct;
+}
+
+- (BOOL)horizontal { return horizontal; }
+- (void)setHorizontal:(BOOL)b {
+ horizontal = b;
+}
+
+- (NSPoint)shiftPoint { return shiftPoint; }
+- (void)setShiftPoint:(NSPoint)p {
+ shiftPoint = p;
+}
+
+- (NSSet*)affectedNodes { return affectedNodes; }
+
+- (void)setAffectedNodes:(NSSet*)set {
+ if (affectedNodes != set) {
+ [affectedNodes release];
+ affectedNodes = [[NSSet alloc] initWithSet:set];
+ }
+}
+
+- (NSSet*)affectedEdges { return affectedEdges; }
+
+- (void)setAffectedEdges:(NSSet*)set {
+ if (affectedEdges != set) {
+ [affectedEdges release];
+ affectedEdges = [[NSSet alloc] initWithSet:set];
+ }
+}
+
+- (Node*)nodeRef { return nodeRef; }
+
+- (void)setNodeRef:(Node*)nd {
+ if (nodeRef != nd) {
+ [nodeRef release];
+ nodeRef = [nd retain];
+ }
+}
+
+- (Node*)oldNode { return oldNode; }
+
+- (void)setOldNode:(Node*)nd {
+ if (oldNode != nd) {
+ [oldNode release];
+ oldNode = [nd copy];
+ }
+}
+
+- (Node*)nwNode { return nwNode; }
+
+- (void)setNwNode:(Node*)nd {
+ if (nwNode != nd) {
+ [nwNode release];
+ nwNode = [nd copy];
+ }
+}
+
+- (Edge*)edgeRef { return edgeRef; }
+
+- (void)setEdgeRef:(Edge*)ed {
+ if (edgeRef != ed) {
+ [edgeRef release];
+ edgeRef = [ed retain];
+ }
+}
+
+- (Edge*)oldEdge { return oldEdge; }
+
+- (void)setOldEdge:(Edge*)ed {
+ if (oldEdge != ed) {
+ [oldEdge release];
+ oldEdge = [ed copy];
+ }
+}
+
+- (Edge*)nwEdge { return nwEdge; }
+
+- (void)setNwEdge:(Edge*)ed {
+ if (nwEdge != ed) {
+ [nwEdge release];
+ nwEdge = [ed copy];
+ }
+}
+
+- (BasicMapTable*)oldNodeTable { return oldNodeTable; }
+
+- (void)setOldNodeTable:(BasicMapTable*)tab {
+ if (oldNodeTable != tab) {
+ [oldNodeTable release];
+ oldNodeTable = [tab retain];
+ }
+}
+
+- (BasicMapTable*)nwNodeTable { return nwNodeTable; }
+
+- (void)setNwNodeTable:(BasicMapTable*)tab {
+ if (nwNodeTable != tab) {
+ [nwNodeTable release];
+ nwNodeTable = [tab retain];
+ }
+}
+
+- (NSRect)oldBoundingBox { return oldBoundingBox; }
+
+- (void)setOldBoundingBox:(NSRect)bbox {
+ oldBoundingBox = bbox;
+}
+
+- (NSRect)nwBoundingBox { return nwBoundingBox; }
+
+- (void)setNwBoundingBox:(NSRect)bbox {
+ nwBoundingBox = bbox;
+}
+
+- (GraphElementData*)oldGraphData {
+ return oldGraphData;
+}
+
+- (void)setOldGraphData:(GraphElementData*)data {
+ id origOGD = oldGraphData;
+ oldGraphData = [data copy];
+ [origOGD release];
+}
+
+- (GraphElementData*)nwGraphData {
+ return nwGraphData;
+}
+
+- (void)setNwGraphData:(GraphElementData*)data {
+ id origNGD = nwGraphData;
+ nwGraphData = [data copy];
+ [origNGD release];
+}
+
+- (GraphChange*)invert {
+ GraphChange *inverse = [[GraphChange alloc] init];
+ switch ([self changeType]) {
+ case GraphAddition:
+ [inverse setChangeType:GraphDeletion];
+ [inverse setAffectedNodes:[self affectedNodes]];
+ [inverse setAffectedEdges:[self affectedEdges]];
+ break;
+ case GraphDeletion:
+ [inverse setChangeType:GraphAddition];
+ [inverse setAffectedNodes:[self affectedNodes]];
+ [inverse setAffectedEdges:[self affectedEdges]];
+ break;
+ case NodePropertyChange:
+ [inverse setChangeType:NodePropertyChange];
+ [inverse setNodeRef:[self nodeRef]];
+ [inverse setOldNode:[self nwNode]];
+ [inverse setNwNode:[self oldNode]];
+ break;
+ case NodesPropertyChange:
+ [inverse setChangeType:NodesPropertyChange];
+ [inverse setOldNodeTable:[self nwNodeTable]];
+ [inverse setNwNodeTable:[self oldNodeTable]];
+ break;
+ case EdgePropertyChange:
+ [inverse setChangeType:EdgePropertyChange];
+ [inverse setEdgeRef:[self edgeRef]];
+ [inverse setOldEdge:[self nwEdge]];
+ [inverse setNwEdge:[self oldEdge]];
+ break;
+ case NodesShift:
+ [inverse setChangeType:NodesShift];
+ [inverse setAffectedNodes:[self affectedNodes]];
+ [inverse setShiftPoint:NSMakePoint(-[self shiftPoint].x,
+ -[self shiftPoint].y)];
+ break;
+ case NodesFlip:
+ [inverse setChangeType:NodesFlip];
+ [inverse setAffectedNodes:[self affectedNodes]];
+ [inverse setHorizontal:[self horizontal]];
+ break;
+ case BoundingBoxChange:
+ [inverse setChangeType:BoundingBoxChange];
+ [inverse setOldBoundingBox:[self nwBoundingBox]];
+ [inverse setNwBoundingBox:[self oldBoundingBox]];
+ break;
+ case GraphPropertyChange:
+ [inverse setChangeType:GraphPropertyChange];
+ [inverse setOldGraphData:[self nwGraphData]];
+ [inverse setNwGraphData:[self oldGraphData]];
+ break;
+ }
+
+ return [inverse autorelease];
+}
+
+- (void)dealloc {
+ [affectedNodes release];
+ [affectedEdges release];
+ [nodeRef release];
+ [oldNode release];
+ [nwNode release];
+ [edgeRef release];
+ [oldEdge release];
+ [oldNodeTable release];
+ [nwNodeTable release];
+
+ [super dealloc];
+}
+
++ (GraphChange*)graphAdditionWithNodes:(NSSet *)ns edges:(NSSet *)es {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:GraphAddition];
+ [gc setAffectedNodes:ns];
+ [gc setAffectedEdges:es];
+ return [gc autorelease];
+}
+
++ (GraphChange*)graphDeletionWithNodes:(NSSet *)ns edges:(NSSet *)es {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:GraphDeletion];
+ [gc setAffectedNodes:ns];
+ [gc setAffectedEdges:es];
+ return [gc autorelease];
+}
+
++ (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];
+ return [gc autorelease];
+}
+
++ (GraphChange*)propertyChangeOfNodesFromOldCopies:(BasicMapTable*)oldC
+ toNewCopies:(BasicMapTable*)newC {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:NodesPropertyChange];
+ [gc setOldNodeTable:oldC];
+ [gc setNwNodeTable:newC];
+ return [gc autorelease];
+}
+
++ (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];
+ return [gc autorelease];
+}
+
++ (GraphChange*)shiftNodes:(NSSet*)ns byPoint:(NSPoint)p {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:NodesShift];
+ [gc setAffectedNodes:ns];
+ [gc setShiftPoint:p];
+ return [gc autorelease];
+}
+
++ (GraphChange*)flipNodes:(NSSet*)ns horizontal:(BOOL)b {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:NodesFlip];
+ [gc setAffectedNodes:ns];
+ [gc setHorizontal:b];
+ return [gc autorelease];
+}
+
++ (GraphChange*)changeBoundingBoxFrom:(NSRect)oldBB to:(NSRect)newBB {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:BoundingBoxChange];
+ [gc setOldBoundingBox:oldBB];
+ [gc setNwBoundingBox:newBB];
+ return [gc autorelease];
+}
+
++ (GraphChange*)propertyChangeOfGraphFrom:(GraphElementData*)oldData to:(GraphElementData*)newData {
+ GraphChange *gc = [[GraphChange alloc] init];
+ [gc setChangeType:GraphPropertyChange];
+ [gc setOldGraphData:oldData];
+ [gc setNwGraphData:newData];
+ return [gc autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/GraphElementData.h b/tikzit/src/common/GraphElementData.h
new file mode 100644
index 0000000..1136dbd
--- /dev/null
+++ b/tikzit/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;
+}
+
+
+/*!
+ @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*)stringList;
+
+- (id)copy;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/GraphElementData.m b/tikzit/src/common/GraphElementData.m
new file mode 100644
index 0000000..02b747b
--- /dev/null
+++ b/tikzit/src/common/GraphElementData.m
@@ -0,0 +1,141 @@
+//
+// 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)init {
+ [super init];
+ 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];
+}
+- (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];
+}
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id *)stackbuf
+ count:(NSUInteger)len {
+ 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];
+ [m release];
+
+ GraphElementProperty *p;
+ if (idx == NSNotFound) {
+ p = [[GraphElementProperty alloc] initWithPropertyValue:val forKey:key];
+ [properties addObject:p];
+ [p release];
+ } else {
+ p = [properties objectAtIndex:idx];
+ [p setValue:val];
+ }
+}
+
+- (void)unsetProperty:(NSString*)key {
+ GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key];
+ [properties removeObject:m];
+ [m release];
+}
+
+- (NSString*)propertyForKey:(NSString*)key {
+ GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key];
+ NSInteger idx = [properties indexOfObject:m];
+ [m release];
+
+ 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];
+ [a release];
+}
+
+- (void)unsetAtom:(NSString*)atom {
+ GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom];
+ [properties removeObject:a];
+ [a release];
+}
+
+- (BOOL)isAtomSet:(NSString*)atom {
+ GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom];
+ BOOL set = [properties containsObject:a];
+ [a release];
+ return set;
+}
+
+- (NSString*)stringList {
+ NSString *s = [properties componentsJoinedByString:@", "];
+ return ([s isEqualToString:@""]) ? @"" : [NSString stringWithFormat:@"[%@]", s];
+}
+
+- (id)copy {
+ GraphElementData *cp = [[GraphElementData alloc] init];
+ NSEnumerator *en = [properties objectEnumerator];
+ GraphElementProperty *p, *p2;
+ while ((p = [en nextObject]) != nil) {
+ p2 = [p copy];
+ [cp addObject:p2];
+ [p2 release];
+ }
+ return cp;
+}
+
+- (void)dealloc {
+ [properties release];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/GraphElementProperty.h b/tikzit/src/common/GraphElementProperty.h
new file mode 100644
index 0000000..e8fe2a2
--- /dev/null
+++ b/tikzit/src/common/GraphElementProperty.h
@@ -0,0 +1,91 @@
+//
+// 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 {
+ NSString *key;
+ NSString *value;
+ BOOL isAtom;
+ BOOL isKeyMatch;
+}
+
+@property (readwrite,retain) NSString *key;
+@property (readwrite,retain) 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;
+
+/*!
+ @brief Initialize a new atomic property.
+ @param n the atom's name
+ @result An atom.
+ */
+- (id)initWithAtomName:(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;
+
+/*!
+ @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;
+
+/*!
+ @brief Make a deep copy of this property.
+ @result A new property.
+ */
+- (id)copy;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/GraphElementProperty.m b/tikzit/src/common/GraphElementProperty.m
new file mode 100644
index 0000000..79abe79
--- /dev/null
+++ b/tikzit/src/common/GraphElementProperty.m
@@ -0,0 +1,127 @@
+//
+//
+// 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"
+
+
+@implementation GraphElementProperty
+
+- (id)initWithAtomName:(NSString*)n {
+ [super init];
+ [self setKey:n];
+ [self setValue:nil];
+ isAtom = YES;
+ isKeyMatch = NO;
+ return self;
+}
+
+- (id)initWithPropertyValue:(NSString*)v forKey:(NSString*)k {
+ [super init];
+ [self setKey:k];
+ [self setValue:v];
+ isAtom = NO;
+ isKeyMatch = NO;
+ return self;
+}
+
+- (id)initWithKeyMatching:(NSString*)k {
+ [super init];
+ [self setKey:k];
+ [self setValue:nil];
+ isAtom = NO;
+ isKeyMatch = YES;
+ return self;
+}
+
+- (void)setValue:(NSString *)v {
+ if (value != v) {
+ [value release];
+ value = [v copy];
+ }
+}
+
+- (NSString*)value {
+ if (isAtom) {
+ return @"(atom)";
+ } else {
+ return value;
+ }
+}
+
+
+- (void)setKey:(NSString *)k {
+ if (k == nil) k = @""; // don't allow nil keys
+ if (key != k) {
+ [key release];
+ key = [k retain];
+ }
+}
+
+- (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)copy {
+ if (isAtom) {
+ return [[GraphElementProperty alloc] initWithAtomName:[self key]];
+ } else if (isKeyMatch) {
+ return [[GraphElementProperty alloc] initWithKeyMatching:[self key]];
+ } else {
+ return [[GraphElementProperty alloc] initWithPropertyValue:[self value] forKey:[self key]];
+ }
+}
+
+- (NSString*)description {
+ if ([self isAtom]) {
+ return [self key];
+ } else if ([self isKeyMatch]) {
+ return [NSString stringWithFormat:@"%@=*", [self key]];
+ } else {
+ return [NSString stringWithFormat:@"%@=%@", [self key], [self value]];
+ }
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/Grid.h b/tikzit/src/common/Grid.h
new file mode 100644
index 0000000..40bb91e
--- /dev/null
+++ b/tikzit/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 {
+ 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/src/common/Grid.m b/tikzit/src/common/Grid.m
new file mode 100644
index 0000000..4fb2ef8
--- /dev/null
+++ b/tikzit/src/common/Grid.m
@@ -0,0 +1,179 @@
+/*
+ * 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;
+}
+
+- (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];
+}
+
+- (void) dealloc {
+ [transformer release];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/NSError+Tikzit.h b/tikzit/src/common/NSError+Tikzit.h
new file mode 100644
index 0000000..a82db4d
--- /dev/null
+++ b/tikzit/src/common/NSError+Tikzit.h
@@ -0,0 +1,43 @@
+/*
+ * 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
+};
+
+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/src/common/NSError+Tikzit.m b/tikzit/src/common/NSError+Tikzit.m
new file mode 100644
index 0000000..6b9404b
--- /dev/null
+++ b/tikzit/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/src/common/NSFileManager+Utils.h b/tikzit/src/common/NSFileManager+Utils.h
new file mode 100644
index 0000000..75d8926
--- /dev/null
+++ b/tikzit/src/common/NSFileManager+Utils.h
@@ -0,0 +1,29 @@
+//
+// MainWindow.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/src/common/NSFileManager+Utils.m b/tikzit/src/common/NSFileManager+Utils.m
new file mode 100644
index 0000000..2586eb6
--- /dev/null
+++ b/tikzit/src/common/NSFileManager+Utils.m
@@ -0,0 +1,46 @@
+//
+// MainWindow.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/src/common/NSString+LatexConstants.h b/tikzit/src/common/NSString+LatexConstants.h
new file mode 100644
index 0000000..f4b5236
--- /dev/null
+++ b/tikzit/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/src/common/NSString+LatexConstants.m b/tikzit/src/common/NSString+LatexConstants.m
new file mode 100644
index 0000000..1114d40
--- /dev/null
+++ b/tikzit/src/common/NSString+LatexConstants.m
@@ -0,0 +1,204 @@
+//
+// 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 62
+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"
+};
+
+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"
+};
+
+#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 {
+ [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 {
+ [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];
+ [buf release];
+ [wordBuf release];
+
+ return [ret autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/Node.h b/tikzit/src/common/Node.h
new file mode 100644
index 0000000..73a4653
--- /dev/null
+++ b/tikzit/src/common/Node.h
@@ -0,0 +1,134 @@
+//
+// 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 Node
+ @brief A graph node, with associated location and style data.
+ */
+@interface Node : NSObject {
+ NSPoint point;
+ NodeStyle *style;
+ NSString *name;
+ NSString *label;
+ GraphElementData *data;
+}
+
+/*!
+ @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;
+
+/*!
+ @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 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 Copy this node.
+ @result A copy of this node.
+ */
+- (id)copy;
+
+/*!
+ @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/src/common/Node.m b/tikzit/src/common/Node.m
new file mode 100644
index 0000000..3421325
--- /dev/null
+++ b/tikzit/src/common/Node.m
@@ -0,0 +1,173 @@
+//
+// 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"
+
+
+@implementation Node
+
+- (id)initWithPoint:(NSPoint)p {
+ [super init];
+ data = [[GraphElementData alloc] init];
+ style = nil;
+ label = @"";
+ point = p;
+ //[self updateData];
+ return self;
+}
+
+- (id)init {
+ [self initWithPoint:NSMakePoint(0.0f, 0.0f)];
+ return self;
+}
+
+- (BOOL)attachStyleFromTable:(NSArray*)styles {
+ NSString *style_name = [[[data propertyForKey:@"style"] retain] autorelease];
+
+ [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]];
+}
+
+- (id)copy {
+ Node *cp = [[Node alloc] init];
+ [cp setPropertiesFromNode:self];
+ return cp;
+}
+
++ (Node*)nodeWithPoint:(NSPoint)p {
+ return [[[Node alloc] initWithPoint:p] autorelease];
+}
+
++ (Node*)node {
+ return [[[Node alloc] init] autorelease];
+}
+
+
+// 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;
+ }
+}
+
+
+- (NSString*)name {
+ return name;
+}
+
+- (void)setName:(NSString *)s {
+ if (name != s) {
+ [name release];
+ name = [s copy];
+ }
+}
+
+- (NSString*)label {
+ return label;
+}
+
+- (void)setLabel:(NSString *)s {
+ if (label != s) {
+ [label release];
+ label = [s copy];
+ }
+}
+
+- (GraphElementData*)data {
+ return data;
+}
+
+- (void)setData:(GraphElementData*)dt {
+ if (data != dt) {
+ [data release];
+ data = [dt copy];
+ }
+}
+
+- (NSPoint)point {
+ return point;
+}
+
+- (void)setPoint:(NSPoint)value {
+ point = value;
+}
+
+- (NodeStyle*)style {
+ return style;
+}
+
+- (void)setStyle:(NodeStyle *)st {
+ NodeStyle *oldStyle = style;
+ style = [st retain];
+ [oldStyle release];
+ [self updateData];
+}
+
+- (void)dealloc {
+ [self setName:nil];
+ [self setStyle:nil];
+ [self setData:nil];
+ [super dealloc];
+}
+
+- (id)copyWithZone:(NSZone*)z {
+ return nil;
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/NodeStyle.h b/tikzit/src/common/NodeStyle.h
new file mode 100644
index 0000000..4c0e883
--- /dev/null
+++ b/tikzit/src/common/NodeStyle.h
@@ -0,0 +1,118 @@
+//
+// 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 {
+ 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 Stroke color.
+ */
+@property (copy) ColorRGB *strokeColorRGB;
+
+/*!
+ @property fillColorRGB
+ @brief Fill color.
+ */
+@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;
+
+/*!
+ @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;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/NodeStyle.m b/tikzit/src/common/NodeStyle.m
new file mode 100644
index 0000000..d3e8f09
--- /dev/null
+++ b/tikzit/src/common/NodeStyle.m
@@ -0,0 +1,176 @@
+//
+// 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 "ShapeNames.h"
+
+@implementation NodeStyle
+
+
+- (id)init {
+ self = [super initWithNotificationName:@"NodeStylePropertyChanged"];
+ if (self != nil) {
+ strokeThickness = 1;
+ scale = 1.0f;
+ strokeColorRGB = [[ColorRGB alloc] initWithRed:0 green:0 blue:0];
+ fillColorRGB = [[ColorRGB alloc] initWithRed:255 green:255 blue:255];
+
+ name = @"new";
+ category = nil;
+ shapeName = SHAPE_CIRCLE;
+ }
+ return self;
+}
+
+- (id)initWithName:(NSString*)nm {
+ self = [self init];
+ if (self != nil) {
+ [self setName:nm];
+ }
+ return self;
+}
+
++ (NodeStyle*)defaultNodeStyleWithName:(NSString*)nm {
+ return [[[NodeStyle alloc] initWithName:nm] autorelease];
+}
+
+- (NSString*)name {
+ return name;
+}
+
+- (void)setName:(NSString *)s {
+ if (name != s) {
+ NSString *oldValue = name;
+ name = [s copy];
+ [self postPropertyChanged:@"name" oldValue:oldValue];
+ [oldValue release];
+ }
+}
+
+- (NSString*)shapeName {
+ return shapeName;
+}
+
+- (void)setShapeName:(NSString *)s {
+ if (shapeName != s) {
+ NSString *oldValue = shapeName;
+ shapeName = [s copy];
+ [self postPropertyChanged:@"shapeName" oldValue:oldValue];
+ [oldValue release];
+ }
+}
+
+- (NSString*)category {
+ return category;
+}
+
+- (void)setCategory:(NSString *)s {
+ if (category != s) {
+ NSString *oldValue = category;
+ category = [s copy];
+ [self postPropertyChanged:@"category" oldValue:oldValue];
+ [oldValue release];
+ }
+}
+
+- (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];
+ [oldValue release];
+ }
+}
+
+- (ColorRGB*)fillColorRGB {
+ return fillColorRGB;
+}
+
+- (void)setFillColorRGB:(ColorRGB*)c {
+ if (fillColorRGB != c) {
+ ColorRGB *oldValue = fillColorRGB;
+ fillColorRGB = [c copy];
+ [self postPropertyChanged:@"fillColorRGB" oldValue:oldValue];
+ [oldValue release];
+ }
+}
+
+- (NSString*)tikz {
+ NSString *fillName, *strokeName;
+
+ NSMutableString *buf = [NSMutableString string];
+
+ fillName = [fillColorRGB name];
+ strokeName = [strokeColorRGB name];
+
+ // 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];
+
+ [buf appendFormat:@"\\tikzstyle{%@}=[%@,fill=%@,draw=%@%@]\n",
+ name,
+ shapeName,
+ fillName,
+ strokeName,
+ (strokeThickness != 1) ?
+ [NSString stringWithFormat:@",line width=%@ pt",
+ [NSNumber numberWithFloat:(float)strokeThickness * 0.4f]] : @""];
+
+ return buf;
+}
+
+- (BOOL)strokeColorIsKnown {
+ return ([strokeColorRGB name] != nil);
+}
+
+- (BOOL)fillColorIsKnown {
+ return ([fillColorRGB name] != nil);
+}
+
+- (void)dealloc {
+ [self setName:nil];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/PickSupport.h b/tikzit/src/common/PickSupport.h
new file mode 100644
index 0000000..8e71325
--- /dev/null
+++ b/tikzit/src/common/PickSupport.h
@@ -0,0 +1,152 @@
+//
+// 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;
+
+/*!
+ @property selectedEdges
+ @brief A set of selected edges.
+ */
+@property (readonly) NSSet *selectedEdges;
+
+/*!
+ @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/src/common/PickSupport.m b/tikzit/src/common/PickSupport.m
new file mode 100644
index 0000000..5ea13c0
--- /dev/null
+++ b/tikzit/src/common/PickSupport.m
@@ -0,0 +1,171 @@
+//
+// 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) {
+ selectedNodes = [[NSMutableSet set] retain];
+ selectedEdges = [[NSMutableSet set] retain];
+ }
+
+ return self;
+}
+
++ (PickSupport*)pickSupport {
+ return [[[PickSupport alloc] init] autorelease];
+}
+
+- (NSSet*)selectedNodes { return selectedNodes; }
+- (NSSet*)selectedEdges { return selectedEdges; }
+
+- (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) {
+ [selectedNodes release];
+ 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 {
+ [selectedNodes release];
+ [selectedEdges release];
+
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/Preambles.h b/tikzit/src/common/Preambles.h
new file mode 100644
index 0000000..d507ad9
--- /dev/null
+++ b/tikzit/src/common/Preambles.h
@@ -0,0 +1,66 @@
+//
+// 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"
+
+@interface Preambles : NSObject {
+ NSMutableDictionary *preambleDict;
+ NSString *selectedPreambleName;
+ NSArray *styles;
+ 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;
+
+- (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;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/Preambles.m b/tikzit/src/common/Preambles.m
new file mode 100644
index 0000000..4f74dd0
--- /dev/null
+++ b/tikzit/src/common/Preambles.m
@@ -0,0 +1,240 @@
+//
+// 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"
+
+static NSString *PREAMBLE_HEAD =
+@"\\documentclass{article}\n"
+@"\\usepackage[svgnames]{xcolor}\n"
+@"\\usepackage{tikz}\n"
+@"\\pagestyle{empty}\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"
+@"\\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 {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id)init {
+ [super init];
+ selectedPreambleName = @"default";
+ preambleDict = [[NSMutableDictionary alloc] initWithCapacity:1];
+ [preambleDict setObject:[self defaultPreamble] forKey:@"custom"];
+ styles = nil;
+ styleManager = nil;
+ return self;
+}
+
+- (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 {
+ [sty retain];
+ [styles release];
+ styles = sty;
+}
+
+- (NSString*)styleDefinitions {
+ if (styleManager != nil) {
+ [self setStyles:[styleManager nodeStyles]];
+ }
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSMutableString *buf = [NSMutableString string];
+ NSMutableString *colbuf = [NSMutableString string];
+ NSMutableSet *colors = [NSMutableSet setWithCapacity:2*[styles count]];
+ for (NodeStyle *st in styles) {
+ [buf appendFormat:@"%@", [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 ([fill name] == nil && ![colors containsObject:fill]) {
+ [colors addObject:stroke];
+ [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n",
+ [fill hexName], [fill redFloat], [fill greenFloat], [fill blueFloat]];
+ }
+ }
+
+ NSString *defs = [[NSString alloc] initWithFormat:@"%@%@", colbuf, buf];
+
+ [pool drain];
+ return [defs autorelease];
+}
+
+- (NSString*)defaultPreamble {
+ return [NSString stringWithFormat:@"%@%@%@",
+ PREAMBLE_HEAD, [self styleDefinitions], PREAMBLE_TAIL];
+}
+
+- (BOOL)selectedPreambleIsDefault {
+ return [selectedPreambleName isEqualToString:@"default"];
+}
+
+- (NSString*)selectedPreambleName { return selectedPreambleName; }
+- (void)setSelectedPreambleName:(NSString *)sel {
+ if (sel != selectedPreambleName) {
+ [selectedPreambleName release];
+ 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 {
+ [manager retain];
+ [styleManager release];
+ 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];
+ [preamble retain];
+ [preambleDict removeObjectForKey:old];
+ [preambleDict setObject:preamble forKey:new];
+ [preamble release];
+ 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...
+ [name retain];
+ if ([name isEqualToString:selectedPreambleName])
+ [self setSelectedPreambleName:nil];
+ [preambleDict removeObjectForKey:name];
+ [name release];
+ return YES;
+}
+
+- (void)dealloc {
+ [selectedPreambleName release];
+ [styles release];
+ [styleManager release];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/PropertyHolder.h b/tikzit/src/common/PropertyHolder.h
new file mode 100644
index 0000000..ba1d825
--- /dev/null
+++ b/tikzit/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/src/common/PropertyHolder.m b/tikzit/src/common/PropertyHolder.m
new file mode 100644
index 0000000..82b4889
--- /dev/null
+++ b/tikzit/src/common/PropertyHolder.m
@@ -0,0 +1,68 @@
+//
+// 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 {
+ [super init];
+ notificationName = @"UnknownPropertyChanged";
+ return self;
+}
+
+- (id)initWithNotificationName:(NSString*)n {
+ [super init];
+ 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 {
+ [notificationName release];
+ [super dealloc];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/RColor.h b/tikzit/src/common/RColor.h
new file mode 100644
index 0000000..7f22547
--- /dev/null
+++ b/tikzit/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/src/common/RColor.m b/tikzit/src/common/RColor.m
new file mode 100644
index 0000000..49914fe
--- /dev/null
+++ b/tikzit/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/src/common/RectangleShape.h b/tikzit/src/common/RectangleShape.h
new file mode 100644
index 0000000..3fa0f31
--- /dev/null
+++ b/tikzit/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/src/common/RectangleShape.m b/tikzit/src/common/RectangleShape.m
new file mode 100644
index 0000000..0b3baa0
--- /dev/null
+++ b/tikzit/src/common/RectangleShape.m
@@ -0,0 +1,54 @@
+//
+// 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 {
+ [super init];
+ 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];
+
+ return self;
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/RegularPolyShape.h b/tikzit/src/common/RegularPolyShape.h
new file mode 100644
index 0000000..663561e
--- /dev/null
+++ b/tikzit/src/common/RegularPolyShape.h
@@ -0,0 +1,34 @@
+//
+// 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"
+
+@interface RegularPolyShape : Shape {
+}
+
+- (id)initWithSides:(int)s rotation:(float)r;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/RegularPolyShape.m b/tikzit/src/common/RegularPolyShape.m
new file mode 100644
index 0000000..cd5858c
--- /dev/null
+++ b/tikzit/src/common/RegularPolyShape.m
@@ -0,0 +1,70 @@
+//
+// RegularPolyShape.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 "RegularPolyShape.h"
+#import "Node.h"
+#import "Edge.h"
+#import "util.h"
+
+@implementation RegularPolyShape
+
+- (id)initWithSides:(int)sides rotation:(float)rotation {
+ [super init];
+
+ float rad = 0.25f;
+
+ NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:sides];
+ NSMutableArray *edges = [NSMutableArray arrayWithCapacity:sides];
+
+ float dtheta = (M_PI * 2.0f) / ((float)sides);
+ float theta = rotation;
+ int i;
+ float maxY=0.0f, minY=0.0f;
+ NSPoint p;
+ for (i = 0; i < sides; ++i) {
+ p.x = rad * cos(theta);
+ p.y = rad * sin(theta);
+ if (p.y<minY) minY = p.y;
+ if (p.y>maxY) maxY = p.y;
+
+ [nodes addObject:[Node nodeWithPoint:p]];
+ theta += dtheta;
+ }
+
+ float dy = (minY + maxY) / 2.0f;
+
+ for (i = 0; i < sides; ++i) {
+ p = [[nodes objectAtIndex:i] point];
+ p.y -= dy;
+ [[nodes objectAtIndex:i] setPoint:p];
+ [edges addObject:[Edge edgeWithSource:[nodes objectAtIndex:i]
+ andTarget:[nodes objectAtIndex:(i+1)%sides]]];
+ }
+
+ paths = [[NSSet alloc] initWithObjects:edges,nil];
+ return self;
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/RenderContext.h b/tikzit/src/common/RenderContext.h
new file mode 100644
index 0000000..8633944
--- /dev/null
+++ b/tikzit/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/src/common/Shape.h b/tikzit/src/common/Shape.h
new file mode 100644
index 0000000..194a88b
--- /dev/null
+++ b/tikzit/src/common/Shape.h
@@ -0,0 +1,42 @@
+//
+// 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
+}
+
+@property (retain) NSSet *paths;
+@property (readonly) NSRect boundingRect;
+
+- (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/src/common/Shape.m b/tikzit/src/common/Shape.m
new file mode 100644
index 0000000..8714031
--- /dev/null
+++ b/tikzit/src/common/Shape.m
@@ -0,0 +1,141 @@
+//
+// 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 "Edge.h"
+#import "Shape.h"
+#import "SupportDir.h"
+#import "ShapeNames.h"
+#import "CircleShape.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 {
+ [super init];
+ paths = nil;
+ return self;
+}
+
+- (NSSet*)paths {return paths;}
+- (void)setPaths:(NSSet *)p {
+ if (paths != p) {
+ [paths release];
+ paths = [p retain];
+ [self calcBoundingRect];
+ }
+}
+
+- (NSRect)boundingRect { return boundingRect; }
+
+- (id)copyWithZone:(NSZone*)zone {
+ Shape *cp = [[[self class] allocWithZone:zone] init];
+ [cp setPaths:paths];
+ return cp;
+}
+
+- (void)dealloc {
+ [paths release];
+ [super dealloc];
+}
+
+NSDictionary *shapeDictionary = nil;
+
++ (void)addShapesInDir:(NSString*)shapeDir to:(NSMutableDictionary*)shapeDict {
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSArray *files = [fileManager directoryContentsAtPath:shapeDir];
+
+ 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];
+ [sh release];
+ }
+ }
+ }
+ }
+}
+
++ (void)refreshShapeDictionary {
+ Shape *shapes[5] = {
+ [[CircleShape alloc] init],
+ [[RectangleShape alloc] init],
+ [[RegularPolyShape alloc] initWithSides:4 rotation:(M_PI/2.0f)],
+ [[RegularPolyShape alloc] initWithSides:3 rotation:(M_PI/2.0f)],
+ [[RegularPolyShape alloc] initWithSides:3 rotation:(-M_PI/2.0f)]};
+ 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];
+ for (int i = 0; i<5; ++i) [shapes[i] release];
+
+ NSString *systemShapeDir = [[SupportDir systemSupportDir] stringByAppendingPathComponent:@"shapes"];
+ NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"];
+
+ [Shape addShapesInDir:systemShapeDir to:shapeDict];
+ [Shape addShapesInDir:userShapeDir to:shapeDict];
+
+ shapeDictionary = shapeDict;
+
+ [[NSNotificationCenter defaultCenter]
+ postNotificationName:@"ShapeDictionaryReplaced"
+ object:self];
+}
+
++ (NSDictionary*)shapeDictionary {
+ if (shapeDictionary == nil) [Shape refreshShapeDictionary];
+ return shapeDictionary;
+}
+
++ (Shape*)shapeForName:(NSString*)shapeName {
+ Shape *s = [[[Shape shapeDictionary] objectForKey:shapeName] copy];
+ return [s autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/ShapeNames.h b/tikzit/src/common/ShapeNames.h
new file mode 100644
index 0000000..66ecfb1
--- /dev/null
+++ b/tikzit/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/src/common/StyleManager.h b/tikzit/src/common/StyleManager.h
new file mode 100644
index 0000000..406a86a
--- /dev/null
+++ b/tikzit/src/common/StyleManager.h
@@ -0,0 +1,51 @@
+/*
+ * 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 {
+ NSMutableArray *nodeStyles;
+ NodeStyle *activeNodeStyle;
+ NSMutableArray *edgeStyles;
+ EdgeStyle *activeEdgeStyle;
+}
+
++ (StyleManager*) manager;
+- (id) init;
+
+@property (readonly) NSArray *nodeStyles;
+@property (retain) NodeStyle *activeNodeStyle;
+@property (readonly) NSArray *edgeStyles;
+@property (retain) EdgeStyle *activeEdgeStyle;
+
+// 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;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/StyleManager.m b/tikzit/src/common/StyleManager.m
new file mode 100644
index 0000000..b5348c6
--- /dev/null
+++ b/tikzit/src/common/StyleManager.m
@@ -0,0 +1,339 @@
+/*
+ * 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 {
+ return [[[self alloc] init] autorelease];
+}
+
+- (id) init {
+ self = [super init];
+
+ if (self) {
+ // we lazily load the default styles, since they may not be needed
+ nodeStyles = nil;
+ activeNodeStyle = nil;
+ edgeStyles = nil;
+ activeEdgeStyle = nil;
+ }
+
+ return self;
+}
+
+- (void) dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [nodeStyles release];
+ [edgeStyles release];
+
+ [super dealloc];
+}
+
+- (void) loadDefaultEdgeStyles {
+}
+
+- (void) loadDefaultNodeStyles {
+ [nodeStyles release];
+ 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];
+}
+
+- (void) postActiveNodeStyleChanged {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"ActiveNodeStyleChanged" object:self];
+}
+
+- (void) postActiveEdgeStyleChanged {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"ActiveEdgeStyleChanged" 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];
+ [nodeStyles release];
+ [styles retain];
+ nodeStyles = styles;
+ NodeStyle *oldActiveStyle = activeNodeStyle;
+ activeNodeStyle = nil;
+ for (NodeStyle *style in styles) {
+ [self listenToNodeStyle:style];
+ }
+ [self postNodeStylesReplaced];
+ if (oldActiveStyle != activeNodeStyle) {
+ [self postActiveNodeStyleChanged];
+ }
+}
+
+- (void) _setEdgeStyles:(NSMutableArray*)styles {
+ [self ignoreAllEdgeStyles];
+ [edgeStyles release];
+ [styles retain];
+ edgeStyles = styles;
+ EdgeStyle *oldActiveStyle = activeEdgeStyle;
+ activeEdgeStyle = nil;
+ for (EdgeStyle *style in styles) {
+ [self listenToEdgeStyle:style];
+ }
+ [self postEdgeStylesReplaced];
+ if (oldActiveStyle != activeEdgeStyle) {
+ [self postActiveEdgeStyleChanged];
+ }
+}
+
+- (NodeStyle*) activeNodeStyle {
+ if (nodeStyles == nil) {
+ [self loadDefaultNodeStyles];
+ }
+ return activeNodeStyle;
+}
+
+- (void) setActiveNodeStyle:(NodeStyle*)style {
+ if (style == activeNodeStyle) {
+ return;
+ }
+ if (style == nil || [nodeStyles containsObject:style]) {
+ activeNodeStyle = style;
+ [self postActiveNodeStyleChanged];
+ }
+}
+
+- (EdgeStyle*) activeEdgeStyle {
+ if (edgeStyles == nil) {
+ [self loadDefaultEdgeStyles];
+ }
+ return activeEdgeStyle;
+}
+
+- (void) setActiveEdgeStyle:(EdgeStyle*)style {
+ if (style == activeEdgeStyle) {
+ return;
+ }
+ if (style == nil || [edgeStyles containsObject:style]) {
+ activeEdgeStyle = style;
+ [self postActiveEdgeStyleChanged];
+ }
+}
+
+- (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];
+ }
+ if (activeNodeStyle == style) {
+ [self setActiveNodeStyle:nil];
+ }
+
+ [self ignoreNodeStyle:style];
+ [style retain];
+ [nodeStyles removeObject:style];
+ [self postNodeStyleRemoved:style];
+ [style release];
+}
+
+- (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];
+ }
+ if (activeEdgeStyle == style) {
+ [self setActiveEdgeStyle:nil];
+ }
+
+ [self ignoreEdgeStyle:style];
+ [style retain];
+ [edgeStyles removeObject:style];
+ [self postEdgeStyleRemoved:style];
+ [style release];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/SupportDir.h b/tikzit/src/common/SupportDir.h
new file mode 100644
index 0000000..30ccbcb
--- /dev/null
+++ b/tikzit/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/src/common/SupportDir.m b/tikzit/src/common/SupportDir.m
new file mode 100644
index 0000000..c014f4d
--- /dev/null
+++ b/tikzit/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>
+#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; // TODO: improve + support windows
+#endif
+}
+
++ (void)createUserSupportDir {
+#ifdef __APPLE__
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSError *error = nil;
+ [fileManager createDirectoryAtPath:[SupportDir userSupportDir]
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:NULL];
+#else
+ // NSFileManager is slightly dodgy on Windows
+ g_mkdir_with_parents ([[SupportDir userSupportDir] UTF8String], 700);
+#endif
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/TikzGraphAssembler.h b/tikzit/src/common/TikzGraphAssembler.h
new file mode 100644
index 0000000..e976405
--- /dev/null
+++ b/tikzit/src/common/TikzGraphAssembler.h
@@ -0,0 +1,58 @@
+//
+// 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"
+
+@interface TikzGraphAssembler : NSObject {
+ Graph *graph;
+ Node *currentNode;
+ Edge *currentEdge;
+ NSMutableDictionary *nodeMap;
+}
+
+@property (readonly) Graph *graph;
+@property (readonly) GraphElementData *data;
+@property (readonly) Node *currentNode;
+@property (readonly) Edge *currentEdge;
+
+
+- (BOOL)parseTikz:(NSString*)tikz;
+- (BOOL)parseTikz:(NSString*)tikz forGraph:(Graph*)gr;
+
+- (void)prepareNode;
+- (void)finishNode;
+
+- (void)prepareEdge;
+- (void)setEdgeSource:(NSString*)src target:(NSString*)targ;
+- (void)finishEdge;
+
+- (void)invalidate;
+
++ (void)setup;
++ (TikzGraphAssembler*)currentAssembler;
++ (TikzGraphAssembler*)assembler;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/TikzGraphAssembler.m b/tikzit/src/common/TikzGraphAssembler.m
new file mode 100644
index 0000000..5354710
--- /dev/null
+++ b/tikzit/src/common/TikzGraphAssembler.m
@@ -0,0 +1,169 @@
+//
+// 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"
+
+extern int yyparse(void);
+extern int yylex(void);
+extern int yy_scan_string(const char* yy_str);
+extern void yy_delete_buffer(int b);
+extern int yylex_destroy(void);
+
+
+static NSLock *parseLock = nil;
+static id currentAssembler = nil;
+
+void yyerror(const char *str) {
+ NSLog(@"Parse error: %s", str);
+ if (currentAssembler != nil) {
+ [currentAssembler invalidate];
+ }
+}
+
+int yywrap() {
+ return 1;
+}
+
+@implementation TikzGraphAssembler
+
+- (id)init {
+ [super init];
+ graph = nil;
+ currentNode = nil;
+ currentEdge = nil;
+ nodeMap = nil;
+ return self;
+}
+
+- (Graph*)graph { return graph; }
+
+- (GraphElementData *)data {
+ if (currentNode != nil) {
+ return [currentNode data];
+ } else if (currentEdge != nil) {
+ return [currentEdge data];
+ } else {
+ return [graph data];
+ }
+}
+
+- (Node*)currentNode { return currentNode; }
+- (Edge*)currentEdge { return currentEdge; }
+
+- (BOOL)parseTikz:(NSString *)tikz {
+ return [self parseTikz:tikz forGraph:[Graph graph]];
+}
+
+- (BOOL)parseTikz:(NSString*)tikz forGraph:(Graph*)gr {
+ [parseLock lock];
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ currentAssembler = self;
+
+ // set the current graph
+ if (graph != gr) {
+ [graph release];
+ graph = [gr retain];
+ }
+
+ // the node map keeps track of the mapping of names to nodes
+ nodeMap = [[NSMutableDictionary alloc] init];
+
+ // do the parsing
+ yy_scan_string([tikz UTF8String]);
+ yyparse();
+ yylex_destroy();
+
+ [nodeMap release];
+
+ currentAssembler = nil;
+ [pool drain];
+
+ [parseLock unlock];
+
+ return (graph != nil);
+}
+
+- (void)prepareNode {
+ currentNode = [[Node alloc] init];
+}
+
+- (void)finishNode {
+ if (currentEdge != nil) { // this is an edge node
+ [currentEdge setEdgeNode:currentNode];
+ } else { // this is a normal node
+ [graph addNode:currentNode];
+ [nodeMap setObject:currentNode forKey:[currentNode name]];
+ }
+
+ [currentNode release];
+ currentNode = nil;
+}
+
+- (void)prepareEdge {
+ currentEdge = [[Edge alloc] init];
+}
+
+- (void)finishEdge {
+ [currentEdge setAttributesFromData];
+ [graph addEdge:currentEdge];
+ [currentEdge release];
+ currentEdge = nil;
+}
+
+- (void)setEdgeSource:(NSString*)src target:(NSString*)targ {
+ if (![targ isEqualToString:@""]) {
+ [currentEdge setSource:[nodeMap objectForKey:src]];
+ [currentEdge setTarget:[nodeMap objectForKey:targ]];
+ } else {
+ Node *s = [nodeMap objectForKey:src];
+ [currentEdge setSource:s];
+ [currentEdge setTarget:s];
+ }
+}
+
+- (void)dealloc {
+ [graph release];
+ [super dealloc];
+}
+
+- (void)invalidate {
+ [graph release];
+ graph = nil;
+}
+
++ (void)setup {
+ parseLock = [[NSLock alloc] init];
+}
+
++ (TikzGraphAssembler*)currentAssembler {
+ return currentAssembler;
+}
+
++ (TikzGraphAssembler*)assembler {
+ return [[[TikzGraphAssembler alloc] init] autorelease];
+}
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/TikzShape.h b/tikzit/src/common/TikzShape.h
new file mode 100644
index 0000000..1ccf658
--- /dev/null
+++ b/tikzit/src/common/TikzShape.h
@@ -0,0 +1,34 @@
+//
+// 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 {
+}
+
+- (id)initWithTikzFile:(NSString*)file;
+
+@end
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/TikzShape.m b/tikzit/src/common/TikzShape.m
new file mode 100644
index 0000000..43bd0d8
--- /dev/null
+++ b/tikzit/src/common/TikzShape.m
@@ -0,0 +1,87 @@
+//
+// 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 "TikzGraphAssembler.h"
+#import "Graph.h"
+
+NSString *defaultTikz =
+@"\\begin{tikzpicture}\n"
+@" \\begin{pgfonlayer}{nodelayer}\n"
+@" \\node [style=none] (0) at (-0.5, 1) {};\n"
+@" \\node [style=none] (1) at (0.5, 1) {};\n"
+@" \\node [style=none] (2) at (-1.5, -1) {};\n"
+@" \\node [style=none] (3) at (-0.5, -1) {};\n"
+@" \\node [style=none] (4) at (0.5, -1) {};\n"
+@" \\node [style=none] (5) at (1.5, -1) {};\n"
+@" \\end{pgfonlayer}\n"
+@" \\begin{pgfonlayer}{edgelayer}\n"
+@" \\draw (3.center) to (2.center);\n"
+@" \\draw [in=90, out=90, looseness=2.00] (4.center) to (3.center);\n"
+@" \\draw (5.center) to (4.center);\n"
+@" \\draw [in=270, out=90, looseness=0.75] (2.center) to (0.center);\n"
+@" \\draw [in=90, out=-90, looseness=0.75] (1.center) to (5.center);\n"
+@" \\draw (0.center) to (1.center);\n"
+@" \\end{pgfonlayer}\n"
+@"\\end{tikzpicture}\n";
+
+@implementation TikzShape
+
+- (id)initWithTikzFile:(NSString*)file {
+ [super init];
+
+ NSString *tikz = [NSString stringWithContentsOfFile:file
+ encoding:NSUTF8StringEncoding
+ error:NULL];
+ if (tikz == nil) return nil;
+
+ TikzGraphAssembler *ass = [[TikzGraphAssembler alloc] init];
+ [ass parseTikz:tikz];
+
+ Graph *graph = [ass graph];
+ [ass release];
+ 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];
+ paths = [[graph pathCover] retain];
+
+ return self;
+}
+
+
+@end
+
+// vi:ft=objc:ts=4:noet:sts=4:sw=4
diff --git a/tikzit/src/common/Transformer.h b/tikzit/src/common/Transformer.h
new file mode 100644
index 0000000..1b0108a
--- /dev/null
+++ b/tikzit/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/src/common/Transformer.m b/tikzit/src/common/Transformer.m
new file mode 100644
index 0000000..fe4d135
--- /dev/null
+++ b/tikzit/src/common/Transformer.m
@@ -0,0 +1,221 @@
+//
+// 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 {
+ return [[[Transformer alloc] init] autorelease];
+}
+
++ (Transformer*)transformerWithTransformer:(Transformer*)t {
+ return [[t copy] autorelease];
+}
+
++ (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;
+}
+
+- (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;
+}
+
+- (id)copyWithZone:(NSZone *)zone {
+ Transformer *cp = [[[self class] allocWithZone:zone] init];
+ cp->origin = origin;
+ cp->x_scale = x_scale;
+ cp->y_scale = y_scale;
+ return cp;
+}
+
+- (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/src/common/test/color.m b/tikzit/src/common/test/color.m
new file mode 100644
index 0000000..e7a80ba
--- /dev/null
+++ b/tikzit/src/common/test/color.m
@@ -0,0 +1,76 @@
+//
+// 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"
+
+void testColor() {
+ 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];
+} \ No newline at end of file
diff --git a/tikzit/src/common/test/common.m b/tikzit/src/common/test/common.m
new file mode 100644
index 0000000..ee6cb5f
--- /dev/null
+++ b/tikzit/src/common/test/common.m
@@ -0,0 +1,32 @@
+//
+// 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 testCommon() {
+ startTestBlock(@"common");
+ testParser();
+ testColor();
+ endTestBlock(@"common");
+} \ No newline at end of file
diff --git a/tikzit/src/common/test/parser.m b/tikzit/src/common/test/parser.m
new file mode 100644
index 0000000..fc9e76e
--- /dev/null
+++ b/tikzit/src/common/test/parser.m
@@ -0,0 +1,78 @@
+//
+// 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"
+
+void testParser() {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ startTestBlock(@"parser");
+
+ [TikzGraphAssembler setup];
+
+ NodeStyle *rn = [NodeStyle defaultStyleWithName:@"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"]);
+
+ endTestBlock(@"parser");
+
+ [pool drain];
+} \ No newline at end of file
diff --git a/tikzit/src/common/test/test.h b/tikzit/src/common/test/test.h
new file mode 100644
index 0000000..4fddc92
--- /dev/null
+++ b/tikzit/src/common/test/test.h
@@ -0,0 +1,41 @@
+//
+// 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
+
+void setColorEnabled(BOOL b);
+void TEST(NSString *msg, BOOL test);
+
+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]; }
diff --git a/tikzit/src/common/test/test.m b/tikzit/src/common/test/test.m
new file mode 100644
index 0000000..5e05c6e
--- /dev/null
+++ b/tikzit/src/common/test/test.m
@@ -0,0 +1,104 @@
+//
+// 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
+
+void TEST(NSString *msg, BOOL test) {
+ if (test) {
+ PUTS(@"%@[%@PASS%@] %@", INDENT, GREEN, OFF, msg);
+ ++PASSES;
+ } else {
+ PUTS(@"%@[%@FAIL%@] %@", INDENT, RED, OFF, msg);
+ ++FAILS;
+ }
+}
+
+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 = @"";
+ }
+}
diff --git a/tikzit/src/common/tikzlexer.lm b/tikzit/src/common/tikzlexer.lm
new file mode 100644
index 0000000..4fe39d1
--- /dev/null
+++ b/tikzit/src/common/tikzlexer.lm
@@ -0,0 +1,101 @@
+%option nounput
+%{
+//
+// tikzparser.l
+// TikZiT
+//
+// Copyright 2010 Chris Heunen. 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>
+#ifdef __APPLE__
+#include "y.tab.h"
+#else
+#include "tikzparser.h"
+#endif
+
+
+%}
+%%
+\n /* ignore end of line */;
+[ \t]+ /* ignore whitespace */;
+\\begin return LATEXBEGIN;
+\\end return LATEXEND;
+\{tikzpicture\} return TIKZPICTURE;
+\{pgfonlayer\} return PGFONLAYER;
+\.center return ANCHORCENTER;
+\( return LEFTPARENTHESIS;
+\) return RIGHTPARENTHESIS;
+\[ return LEFTBRACKET;
+\] return RIGHTBRACKET;
+; return SEMICOLON;
+, return COMMA;
+= return EQUALS;
+\\draw return DRAW;
+to return TO;
+\\node return NODE;
+\\path return PATH;
+node return ALTNODE;
+rectangle return RECTANGLE;
+at return AT;
+
+[0-9]+ {
+ yylval.nsstr=[NSString stringWithUTF8String:yytext];
+ return NATURALNUMBER;
+}
+
+(\-?[0-9]*\.[0-9]+)|(\-?[0-9]+) {
+ yylval.nsstr=[NSString stringWithUTF8String:yytext];
+ return REALNUMBER;
+}
+
+\\?[a-zA-Z<>\-\']+ { //'
+ yylval.nsstr=[NSString stringWithUTF8String:yytext];
+ return LWORD;
+}
+
+
+\"[^\"]*\" /* " */ {
+ yylval.nsstr=[NSString stringWithUTF8String:yytext];
+ return QUOTEDSTRING;
+}
+
+\{ {
+ NSMutableString *buf = [NSMutableString stringWithString:@"{"];
+ unsigned int brace_depth = 1;
+ while (1) {
+ char c = input();
+ // eof reached before closing brace
+ if (c == '\0' || c == EOF) yyterminate();
+
+ [buf appendFormat:@"%c", c];
+ if (c == '{') brace_depth++;
+ else if (c == '}') {
+ brace_depth--;
+ if (brace_depth == 0) break;
+ }
+ }
+
+ NSString *s = [buf copy];
+ [s autorelease];
+ yylval.nsstr = s;
+ return DELIMITEDSTRING;
+}
+
+%%
diff --git a/tikzit/src/common/tikzparser.ym b/tikzit/src/common/tikzparser.ym
new file mode 100644
index 0000000..e55a881
--- /dev/null
+++ b/tikzit/src/common/tikzparser.ym
@@ -0,0 +1,178 @@
+%expect 6
+
+%{
+//
+// tikzparser.y
+// TikZiT
+//
+// Copyright 2010 Chris Heunen. 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/>.
+//
+#include <stdio.h>
+#include <string.h>
+#import <Foundation/Foundation.h>
+#import "TikzGraphAssembler.h"
+#import "GraphElementProperty.h"
+
+extern int yylex(void);
+extern void yyerror(const char *str);
+
+%}
+
+%union {
+ NSPoint pt;
+ NSString *nsstr;
+};
+
+%token LATEXBEGIN
+%token LATEXEND
+%token TIKZPICTURE
+%token PGFONLAYER
+%token ANCHORCENTER
+%token LEFTPARENTHESIS
+%token RIGHTPARENTHESIS
+%token LEFTBRACKET
+%token RIGHTBRACKET
+%token SEMICOLON
+%token COMMA
+%token EQUALS
+%token DRAW
+%token TO
+%token NODE
+%token RECTANGLE
+%token PATH
+%token ALTNODE
+%token AT
+%token REALNUMBER
+%token NATURALNUMBER
+%token LWORD
+%token QUOTEDSTRING
+%token DELIMITEDSTRING
+
+%type<nsstr> nodename
+%type<nsstr> nodeid
+%type<pt> coords
+%type<nsstr> target
+%type<nsstr> propsym
+%type<nsstr> propsyms
+%type<nsstr> val
+%type<nsstr> number
+
+%%
+
+tikzpicture: LATEXBEGIN TIKZPICTURE optproperties expressions LATEXEND TIKZPICTURE;
+expressions: expressions expression | ;
+expression: node | edge | boundingbox | ignore;
+
+ignore: LATEXBEGIN PGFONLAYER DELIMITEDSTRING | LATEXEND PGFONLAYER;
+
+number: REALNUMBER { $$ = $<nsstr>1; } | NATURALNUMBER { $$ = $<nsstr>1; };
+
+optproperties: LEFTBRACKET properties RIGHTBRACKET | ;
+properties: property extraproperties;
+extraproperties: COMMA property extraproperties | property extraproperties | ;
+
+property:
+ propsyms EQUALS val
+ {
+ TikzGraphAssembler *a = [TikzGraphAssembler currentAssembler];
+ GraphElementProperty *p = [[GraphElementProperty alloc] initWithPropertyValue:$<nsstr>3 forKey:$<nsstr>1];
+ [[a data] addObject:p];
+ [p release];
+ }
+ | propsyms
+ {
+ TikzGraphAssembler *a = [TikzGraphAssembler currentAssembler];
+ GraphElementProperty *p = [[GraphElementProperty alloc] initWithAtomName:$<nsstr>1];
+ [[a data] addObject:p];
+ [p release];
+ };
+
+val: propsyms { $$ = $<nsstr>1; } | QUOTEDSTRING { $$ = $<nsstr>1; };
+propsyms:
+ propsym { $$ = $<nsstr>1; }
+ | propsyms propsym
+ {
+ NSString *s = [$<nsstr>1 stringByAppendingFormat:@" %@", $<nsstr>2];
+ $$ = s;
+ };
+
+propsym:
+ LWORD { $$ = $<nsstr>1; }
+ | number { $$ = $<nsstr>1; };
+
+
+nodecmd : NODE { [[TikzGraphAssembler currentAssembler] prepareNode]; };
+
+node:
+ nodecmd optproperties nodename AT coords nodelabel SEMICOLON
+ {
+ TikzGraphAssembler *a = [TikzGraphAssembler currentAssembler];
+ [[a currentNode] setName:$<nsstr>3];
+ [[a currentNode] setPoint:$<pt>5];
+ [a finishNode];
+ };
+
+nodelabel:
+ DELIMITEDSTRING
+ {
+ Node *n = [[TikzGraphAssembler currentAssembler] currentNode];
+ NSString *label = $<nsstr>1;
+ [n setLabel:[label substringWithRange:NSMakeRange(1, [label length]-2)]];
+ }
+
+optanchor: | ANCHORCENTER;
+nodename: LEFTPARENTHESIS nodeid optanchor RIGHTPARENTHESIS { $$ = $<nsstr>2; };
+nodeid: LWORD { $$ = $<nsstr>1; } | NATURALNUMBER { $$ = $<nsstr>1; };
+
+coords:
+ LEFTPARENTHESIS number COMMA number RIGHTPARENTHESIS
+ {
+ $$ = NSMakePoint([$<nsstr>2 floatValue], [$<nsstr>4 floatValue]);
+ };
+
+edgecmd : DRAW { [[TikzGraphAssembler currentAssembler] prepareEdge]; };
+edge:
+ edgecmd optproperties nodename TO optedgenode target SEMICOLON
+ {
+ TikzGraphAssembler *a = [TikzGraphAssembler currentAssembler];
+ [a setEdgeSource:$<nsstr>3
+ target:$<nsstr>6];
+ [a finishEdge];
+ };
+target: nodename { $$=$<nsstr>1; } | selfloop { $$=@""; };
+selfloop: LEFTPARENTHESIS RIGHTPARENTHESIS;
+
+altnodecmd: ALTNODE { [[TikzGraphAssembler currentAssembler] prepareNode]; };
+optedgenode:
+ | altnodecmd optproperties nodelabel
+ {
+ [[TikzGraphAssembler currentAssembler] finishNode];
+ }
+
+bbox_ignoreprops:
+ | LEFTBRACKET LWORD LWORD LWORD LWORD RIGHTBRACKET;
+
+boundingbox:
+ PATH bbox_ignoreprops coords RECTANGLE coords SEMICOLON
+ {
+ Graph *g = [[TikzGraphAssembler currentAssembler] graph];
+ [g setBoundingBox:NSRectAroundPoints($<pt>3, $<pt>5)];
+ };
+
+%%
diff --git a/tikzit/src/common/util.h b/tikzit/src/common/util.h
new file mode 100644
index 0000000..74871dc
--- /dev/null
+++ b/tikzit/src/common/util.h
@@ -0,0 +1,113 @@
+//
+// 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
+
+/*!
+ @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 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 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 Normalises an angle (in degrees) to fall between -359 and 359
+ */
+int normaliseAngleDeg (int degrees);
+
+/*!
+ @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);
+
+// vi:ft=objc:noet:ts=4:sts=4:sw=4
diff --git a/tikzit/src/common/util.m b/tikzit/src/common/util.m
new file mode 100644
index 0000000..feef76c
--- /dev/null
+++ b/tikzit/src/common/util.m
@@ -0,0 +1,113 @@
+//
+// 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"
+
+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;
+}
+
+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;
+}
+
+int normaliseAngleDeg (int degrees) {
+ while (degrees >= 360) {
+ degrees -= 360;
+ }
+ while (degrees <= -360) {
+ degrees += 360;
+ }
+ return degrees;
+}
+
+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]];
+}
+
+