diff options
Diffstat (limited to 'tikzit/src/common')
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]]; +} + + |