diff options
author | randomguy3 <randomguy3@7c02a99a-9b00-45e3-bf44-6f3dd7fddb64> | 2012-01-09 11:00:50 +0000 |
---|---|---|
committer | randomguy3 <randomguy3@7c02a99a-9b00-45e3-bf44-6f3dd7fddb64> | 2012-01-09 11:00:50 +0000 |
commit | a8a8dfb90d6a51ae369c042c95162f45754c7c4b (patch) | |
tree | 0e7a5f82febebe7129ebfb015f05b114064c39fd /tikzit/src | |
parent | e1cf0babff63e670e0d550b4072c22649a117fa7 (diff) |
Move tikzit into "trunk" directory
git-svn-id: https://tikzit.svn.sourceforge.net/svnroot/tikzit/trunk@365 7c02a99a-9b00-45e3-bf44-6f3dd7fddb64
Diffstat (limited to 'tikzit/src')
203 files changed, 26923 insertions, 0 deletions
diff --git a/tikzit/src/Makefile.am b/tikzit/src/Makefile.am new file mode 100644 index 0000000..37e1914 --- /dev/null +++ b/tikzit/src/Makefile.am @@ -0,0 +1,135 @@ +if WINDOWS +sharedir = ../ +else +sharedir = @datarootdir@/tikzit +endif + +AM_OBJCFLAGS = @GNUSTEPOBJCFLAGS@ \ + @GTK_CFLAGS@ \ + @GDK_PIXBUF_CFLAGS@ \ + @POPPLER_CFLAGS@ \ + -I common \ + -I linux \ + -DTIKZITSHAREDIR=\"$(sharedir)\" \ + -std=c99 \ + -D_GNU_SOURCE +LIBS = @GNUSTEPLIBS@ \ + @GTK_LIBS@ \ + @GDK_PIXBUF_LIBS@ \ + @POPPLER_LIBS@ +AM_YFLAGS = -d +PARSERFILES = common/tikzlexer.m common/tikzparser.m common/tikzparser.h + +bin_PROGRAMS = tikzit +BUILT_SOURCES = $(PARSERFILES) +tikzit_SOURCES = linux/CairoRenderContext.m \ + linux/ColorRGB+IntegerListStorage.m \ + linux/ColorRGB+Gtk.m \ + linux/Configuration.m \ + linux/Edge+Render.m \ + linux/EdgeStyle+Storage.m \ + linux/FileChooserDialog.m \ + linux/GraphInputHandler.m \ + linux/GraphRenderer.m \ + linux/MainWindow.m \ + linux/Menu.m \ + linux/Node+Render.m \ + linux/NodeStyle+Gtk.m \ + linux/NodeStyle+Storage.m \ + linux/NodeStyleEditor.m \ + linux/NodeStyleSelector.m \ + linux/NodeStylesPalette.m \ + linux/NSError+Glib.m \ + linux/NSFileManager+Glib.m \ + linux/NSString+Glib.m \ + linux/PreambleEditor.m \ + linux/Preambles+Storage.m \ + linux/PropertyPane.m \ + linux/PropertyListEditor.m \ + linux/RecentManager.m \ + linux/Shape+Render.m \ + linux/StyleManager+Storage.m \ + linux/TikzDocument.m \ + linux/WidgetSurface.m \ + linux/cairo_helpers.m \ + linux/clipboard.m \ + linux/gtkhelpers.m \ + linux/logo.m \ + linux/mkdtemp.m \ + linux/main.m \ + common/BasicMapTable.m \ + common/CircleShape.m \ + common/ColorRGB.m \ + common/Edge.m \ + common/EdgeStyle.m \ + common/GraphChange.m \ + common/GraphElementData.m \ + common/Graph.m \ + common/Grid.m \ + common/Node.m \ + common/NodeStyle.m \ + common/NSError+Tikzit.m \ + common/NSFileManager+Utils.m \ + common/NSString+LatexConstants.m \ + common/PickSupport.m \ + common/Preambles.m \ + common/PropertyHolder.m \ + common/GraphElementProperty.m \ + common/RColor.m \ + common/RectangleShape.m \ + common/RegularPolyShape.m \ + common/Shape.m \ + common/StyleManager.m \ + common/SupportDir.m \ + common/TikzGraphAssembler.m \ + common/TikzShape.m \ + common/Transformer.m \ + common/tikzparser.m \ + common/tikzlexer.m \ + common/util.m + +if HAVE_POPPLER +tikzit_SOURCES += \ + linux/PreviewRenderer.m \ + linux/PreviewWindow.m +endif + +if WINDOWS +tikzit.res: tikzit.rc + $(AM_V_GEN)windres $^ -O coff -o $@ + +tikzit_LDADD = tikzit.res +CLEANFILES = tikzit.res +endif + +common/tikzlexer.m: common/tikzlexer.lm + $(AM_V_GEN)$(LEX) -o $@ $^ + +common/tikzparser.m: common/tikzparser.ym + $(AM_V_GEN)$(YACC) --defines=common/tikzparser.h --output=$@ $^ + +common/tikzparser.h: common/tikzparser.m + +linux/icondata.m: ../draw-ellipse.png ../draw-path.png ../select-rectangular.png ../transform-crop-and-resize.png ../transform-move.png + $(AM_V_GEN)gdk-pixbuf-csource --struct --static --raw --build-list \ + draw_ellipse ../draw-ellipse.png \ + draw_path ../draw-path.png \ + select_rectangular ../select-rectangular.png \ + transform_crop_and_resize ../transform-crop-and-resize.png \ + transform_move ../transform-move.png \ + > $@ + +linux/logodata.m: ../share/icons/hicolor/16x16/apps/tikzit.png ../share/icons/hicolor/24x24/apps/tikzit.png ../share/icons/hicolor/48x48/apps/tikzit.png ../share/icons/hicolor/64x64/apps/tikzit.png ../share/icons/hicolor/128x128/apps/tikzit.png + $(AM_V_GEN)gdk-pixbuf-csource --struct --static --raw --build-list \ + logo16 ../share/icons/hicolor/16x16/apps/tikzit.png \ + logo24 ../share/icons/hicolor/24x24/apps/tikzit.png \ + logo48 ../share/icons/hicolor/48x48/apps/tikzit.png \ + logo64 ../share/icons/hicolor/64x64/apps/tikzit.png \ + logo128 ../share/icons/hicolor/128x128/apps/tikzit.png \ + > $@ + +linux/Menu.m: linux/icondata.m +linux/logo.m: linux/logodata.m + +EXTRA_DIST = linux/*.h common/*.h $(PARSERFILES) common/tikzlexer.lm common/tikzparser.ym +MAINTAINERCLEANFILES = $(PARSERFILES) 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]]; +} + + diff --git a/tikzit/src/linux/CairoRenderContext.h b/tikzit/src/linux/CairoRenderContext.h new file mode 100644 index 0000000..ac9c5ee --- /dev/null +++ b/tikzit/src/linux/CairoRenderContext.h @@ -0,0 +1,59 @@ +/* + * 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 "TZFoundation.h" +#import "RenderContext.h" +#import "Transformer.h" +#import <cairo/cairo.h> +#import <pango/pango.h> +#import <gtk/gtk.h> + +@interface PangoTextLayout: NSObject<TextLayout> { + PangoLayout *layout; + cairo_t *context; +} + ++ (PangoTextLayout*) layoutForContext:(cairo_t*)cr withFontSize:(CGFloat)fontSize; +- (id) initWithContext:(cairo_t*)cr fontSize:(CGFloat)fontSize; +- (void) setText:(NSString*)text; + +@end + +@interface CairoRenderContext: NSObject<RenderContext> { + cairo_t *context; +} + ++ (CairoRenderContext*) contextForSurface:(cairo_surface_t*)surface; +- (id) initForSurface:(cairo_surface_t*)surface; + ++ (CairoRenderContext*) contextForWidget:(GtkWidget*)widget; +- (id) initForWidget:(GtkWidget*)widget; + ++ (CairoRenderContext*) contextForDrawable:(GdkDrawable*)d; +- (id) initForDrawable:(GdkDrawable*)d; + ++ (CairoRenderContext*) contextForPixbuf:(GdkPixbuf*)buf; +- (id) initForPixbuf:(GdkPixbuf*)buf; + +- (cairo_t*) cairoContext; +- (void) applyTransform:(Transformer*)transformer; + +- (void) clearSurface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/CairoRenderContext.m b/tikzit/src/linux/CairoRenderContext.m new file mode 100644 index 0000000..bed06a6 --- /dev/null +++ b/tikzit/src/linux/CairoRenderContext.m @@ -0,0 +1,346 @@ +/* + * 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 "CairoRenderContext.h" + +#import "cairo_helpers.h" +#import "util.h" + +#import <pango/pangocairo.h> + +@implementation PangoTextLayout + +- (id) init { + [self release]; + self = nil; + return nil; +} + ++ (PangoTextLayout*) layoutForContext:(cairo_t*)cr withFontSize:(CGFloat)fontSize { + return [[[self alloc] initWithContext:cr fontSize:fontSize] autorelease]; +} + +- (id) initWithContext:(cairo_t*)cr fontSize:(CGFloat)fontSize { + self = [super init]; + + if (self) { + cairo_reference (cr); + context = cr; + layout = pango_cairo_create_layout (cr); + + PangoFontDescription *font_desc = pango_font_description_new (); + pango_font_description_set_family_static (font_desc, "Sans"); + pango_font_description_set_size (font_desc, pango_units_from_double (fontSize)); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + } + + return self; +} + +- (void) setText:(NSString*)text { + pango_layout_set_text (layout, [text UTF8String], -1); +} + +- (NSSize) size { + int width, height; + pango_layout_get_size (layout, &width, &height); + return NSMakeSize (pango_units_to_double (width), pango_units_to_double (height)); +} + +- (NSString*) text { + return [NSString stringWithUTF8String:pango_layout_get_text (layout)]; +} + +- (void) showTextAt:(NSPoint)topLeft withColor:(RColor)color { + cairo_save (context); + + cairo_move_to(context, topLeft.x, topLeft.y); + cairo_set_source_rcolor (context, color); + pango_cairo_show_layout (context, layout); + + cairo_restore (context); +} + +- (void) dealloc { + if (layout) + g_object_unref (G_OBJECT (layout)); + if (context) + cairo_destroy (context); + + [super dealloc]; +} + +@end + +@implementation CairoRenderContext + +- (id) init { + [self release]; + self = nil; + return nil; +} + ++ (CairoRenderContext*) contextForSurface:(cairo_surface_t*)surface { + return [[[self alloc] initForSurface:surface] autorelease]; +} + +- (id) initForSurface:(cairo_surface_t*)surface { + self = [super init]; + + if (self) { + context = cairo_create (surface); + } + + return self; +} + ++ (CairoRenderContext*) contextForWidget:(GtkWidget*)widget { + return [[[self alloc] initForWidget:widget] autorelease]; +} + +- (id) initForWidget:(GtkWidget*)widget { + self = [super init]; + + if (self) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + context = gdk_cairo_create (window); + } else { + [self release]; + self = nil; + } + } + + return self; +} + ++ (CairoRenderContext*) contextForDrawable:(GdkDrawable*)d { + return [[[self alloc] initForDrawable:d] autorelease]; +} + +- (id) initForDrawable:(GdkDrawable*)d { + self = [super init]; + + if (self) { + context = gdk_cairo_create (d); + } + + return self; +} + ++ (CairoRenderContext*) contextForPixbuf:(GdkPixbuf*)pixbuf { + return [[[self alloc] initForPixbuf:pixbuf] autorelease]; +} + +- (id) initForPixbuf:(GdkPixbuf*)pixbuf { + self = [super init]; + + if (self) { + cairo_format_t format = -1; + + if (gdk_pixbuf_get_colorspace (pixbuf) != GDK_COLORSPACE_RGB) { + NSLog(@"Unsupported colorspace (must be RGB)"); + [self release]; + return nil; + } + if (gdk_pixbuf_get_bits_per_sample (pixbuf) != 8) { + NSLog(@"Unsupported bits per sample (must be 8)"); + [self release]; + return nil; + } + if (gdk_pixbuf_get_has_alpha (pixbuf)) { + if (gdk_pixbuf_get_n_channels (pixbuf) != 4) { + NSLog(@"Unsupported bits per sample (must be 4 for an image with alpha)"); + [self release]; + return nil; + } + format = CAIRO_FORMAT_ARGB32; + } else { + if (gdk_pixbuf_get_n_channels (pixbuf) != 3) { + NSLog(@"Unsupported bits per sample (must be 3 for an image without alpha)"); + [self release]; + return nil; + } + format = CAIRO_FORMAT_RGB24; + } + + cairo_surface_t *surface = cairo_image_surface_create_for_data( + gdk_pixbuf_get_pixels(pixbuf), + format, + gdk_pixbuf_get_width(pixbuf), + gdk_pixbuf_get_height(pixbuf), + gdk_pixbuf_get_rowstride(pixbuf)); + context = cairo_create (surface); + cairo_surface_destroy (surface); + } + + return self; +} + +- (cairo_t*) cairoContext { + return context; +} + +- (void) applyTransform:(Transformer*)transformer { + NSPoint origin = [transformer toScreen:NSZeroPoint]; + cairo_translate (context, origin.x, origin.y); + NSPoint scale = [transformer toScreen:NSMakePoint (1.0f, 1.0f)]; + scale.x -= origin.x; + scale.y -= origin.y; + cairo_scale (context, scale.x, scale.y); +} + +- (void) saveState { + cairo_save (context); +} + +- (void) restoreState { + cairo_restore (context); +} + +- (NSRect) clipBoundingBox { + double clipx1, clipx2, clipy1, clipy2; + cairo_clip_extents (context, &clipx1, &clipy1, &clipx2, &clipy2); + return NSMakeRect (clipx1, clipy1, clipx2-clipx1, clipy2-clipy1); +} + +- (BOOL) strokeIncludesPoint:(NSPoint)p { + return cairo_in_stroke (context, p.x, p.y); +} + +- (BOOL) fillIncludesPoint:(NSPoint)p { + return cairo_in_fill (context, p.x, p.y); +} + +- (id<TextLayout>) layoutText:(NSString*)text withSize:(CGFloat)fontSize { + PangoTextLayout *layout = [PangoTextLayout layoutForContext:context withFontSize:fontSize]; + [layout setText:text]; + return layout; +} + +- (void) setAntialiasMode:(AntialiasMode)mode { + if (mode == AntialiasDisabled) { + cairo_set_antialias (context, CAIRO_ANTIALIAS_NONE); + } else { + cairo_set_antialias (context, CAIRO_ANTIALIAS_DEFAULT); + } +} + +- (void) setLineWidth:(CGFloat)width { + cairo_set_line_width (context, width); +} + +- (void) setLineDash:(CGFloat)dashLength { + if (dashLength <= 0.0) { + cairo_set_dash (context, NULL, 0, 0); + } else { + double dashes[] = { dashLength }; + cairo_set_dash (context, dashes, 1, 0); + } +} + +// paths +- (void) startPath { + cairo_new_path (context); +} + +- (void) closeSubPath { + cairo_close_path (context); +} + +- (void) moveTo:(NSPoint)p { + cairo_move_to(context, p.x, p.y); +} + +- (void) curveTo:(NSPoint)end withCp1:(NSPoint)cp1 andCp2:(NSPoint)cp2 { + cairo_curve_to (context, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y); +} + +- (void) lineTo:(NSPoint)end { + cairo_line_to (context, end.x, end.y); +} + +- (void) rect:(NSRect)rect { + cairo_rectangle (context, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +- (void) circleAt:(NSPoint)c withRadius:(CGFloat)r { + cairo_new_sub_path (context); + cairo_arc (context, c.x, c.y, r, 0, 2 * M_PI); +} + +- (void) strokePathWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_stroke (context); +} + +- (void) fillPathWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_fill (context); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor { + cairo_set_source_rcolor (context, fcolor); + cairo_fill_preserve (context); + cairo_set_source_rcolor (context, scolor); + cairo_stroke (context); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor + usingAlpha:(CGFloat)alpha { + cairo_push_group (context); + cairo_set_source_rcolor (context, fcolor); + cairo_fill_preserve (context); + cairo_set_source_rcolor (context, scolor); + cairo_stroke (context); + cairo_pop_group_to_source (context); + cairo_paint_with_alpha (context, alpha); +} + +- (void) clipToPath { + cairo_clip (context); +} + +- (void) paintWithColor:(RColor)color { + cairo_set_source_rcolor (context, color); + cairo_paint (context); +} + +- (void) clearSurface { + cairo_operator_t old_op = cairo_get_operator (context); + + cairo_set_operator (context, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba (context, 0.0, 0.0, 0.0, 0.0); + cairo_paint (context); + + cairo_set_operator (context, old_op); +} + +- (void) dealloc { + if (context) { + cairo_destroy (context); + } + + [super dealloc]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/ColorRGB+Gtk.h b/tikzit/src/linux/ColorRGB+Gtk.h new file mode 100644 index 0000000..5cfb4d7 --- /dev/null +++ b/tikzit/src/linux/ColorRGB+Gtk.h @@ -0,0 +1,30 @@ +/* + * 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 "TZFoundation.h" +#import "ColorRGB.h" +#import <gdk/gdk.h> + +@interface ColorRGB (Gtk) + ++ (ColorRGB*) colorWithGdkColor:(GdkColor)color; +- (id) initWithGdkColor:(GdkColor)color; +- (GdkColor) gdkColor; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/ColorRGB+Gtk.m b/tikzit/src/linux/ColorRGB+Gtk.m new file mode 100644 index 0000000..be5dd56 --- /dev/null +++ b/tikzit/src/linux/ColorRGB+Gtk.m @@ -0,0 +1,46 @@ +/* + * 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 "ColorRGB+Gtk.h" + +// 257 = 65535/255 +// GdkColor values run from 0 to 65535, not from 0 to 255 +#define GDK_FACTOR 257 + +@implementation ColorRGB (Gtk) + ++ (ColorRGB*) colorWithGdkColor:(GdkColor)color { + return [ColorRGB colorWithRed:color.red/GDK_FACTOR green:color.green/GDK_FACTOR blue:color.blue/GDK_FACTOR]; +} + +- (id) initWithGdkColor:(GdkColor)color { + return [self initWithRed:color.red/GDK_FACTOR green:color.green/GDK_FACTOR blue:color.blue/GDK_FACTOR]; +} + +- (GdkColor) gdkColor { + GdkColor color; + color.pixel = 0; + color.red = GDK_FACTOR * [self red]; + color.green = GDK_FACTOR * [self green]; + color.blue = GDK_FACTOR * [self blue]; + return color; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/ColorRGB+IntegerListStorage.h b/tikzit/src/linux/ColorRGB+IntegerListStorage.h new file mode 100644 index 0000000..118eaee --- /dev/null +++ b/tikzit/src/linux/ColorRGB+IntegerListStorage.h @@ -0,0 +1,32 @@ +/* + * 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 "TZFoundation.h" +#import "ColorRGB.h" + +/** + * Stores a ColorRGB as a list of short integers + */ +@interface ColorRGB (IntegerListStorage) + ++ (ColorRGB*) colorFromValueList:(NSArray*)values; +- (id) initFromValueList:(NSArray*)values; +- (NSArray*) valueList; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/ColorRGB+IntegerListStorage.m b/tikzit/src/linux/ColorRGB+IntegerListStorage.m new file mode 100644 index 0000000..0103a3c --- /dev/null +++ b/tikzit/src/linux/ColorRGB+IntegerListStorage.m @@ -0,0 +1,57 @@ +/* + * 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 "ColorRGB+IntegerListStorage.h" + +@implementation ColorRGB (IntegerListStorage) + ++ (ColorRGB*) colorFromValueList:(NSArray*)values { + if ([values count] != 3) { + return nil; + } + + unsigned short redValue = [[values objectAtIndex:0] intValue]; + unsigned short greenValue = [[values objectAtIndex:1] intValue]; + unsigned short blueValue = [[values objectAtIndex:2] intValue]; + return [ColorRGB colorWithRed:redValue green:greenValue blue:blueValue]; +} + +- (id) initFromValueList:(NSArray*)values { + if ([values count] != 3) { + [self release]; + return nil; + } + + unsigned short redValue = [[values objectAtIndex:0] intValue]; + unsigned short greenValue = [[values objectAtIndex:1] intValue]; + unsigned short blueValue = [[values objectAtIndex:2] intValue]; + + return [self initWithRed:redValue green:greenValue blue:blueValue]; +} + +- (NSArray*) valueList { + NSMutableArray *array = [NSMutableArray arrayWithCapacity:3]; + [array addObject:[NSNumber numberWithInt:[self red]]]; + [array addObject:[NSNumber numberWithInt:[self green]]]; + [array addObject:[NSNumber numberWithInt:[self blue]]]; + return array; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Configuration.h b/tikzit/src/linux/Configuration.h new file mode 100644 index 0000000..93a74fa --- /dev/null +++ b/tikzit/src/linux/Configuration.h @@ -0,0 +1,447 @@ +// +// 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 "TZFoundation.h" + +/** + * Manages configuration information in a grouped key-value format. + */ +@interface Configuration : NSObject { + NSString *name; + GKeyFile *file; +} + +/** + * Check whether there is any existing configuration. + */ ++ (BOOL) configurationExistsWithName:(NSString*)name; +/** + * Create a blank configuration with the given name, without loading + * any existing configuration information. + * + * @param name the name of the configuration + */ ++ (Configuration*) emptyConfigurationWithName:(NSString*)name; +/** + * Load an existing configuration for the given name. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + */ ++ (Configuration*) configurationWithName:(NSString*)name; +/** + * Load an existing configuration for the given name. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + * @param error this will be set if the configuration exists, but could + * not be opened. + */ ++ (Configuration*) configurationWithName:(NSString*)name loadError:(NSError**)error; + +/** + * Initialise the configuration to be empty + * + * Does not attempt to load any existing configuration data. + * + * @param name the name of the configuration + */ +- (id) initEmptyWithName:(NSString*)name; +/** + * Initialise a configuration, loading it if it had previously been stored. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + */ +- (id) initWithName:(NSString*)name; +/** + * Initialise a configuration, loading it if it had previously been stored. + * + * If there was no existing configuration, or it could not be opened, + * an empty configuration will be returned. + * + * @param name the name of the configuration + * @param error this will be set if the configuration exists, but could + * not be opened. + */ +- (id) initWithName:(NSString*)name loadError:(NSError**)error; + +/** + * The name of the configuration. + * + * Configurations with different names are stored independently. + */ +- (NSString*) name; +/** + * Set the name of the configuration. + * + * This will affect the behaviour of [-writeToStore] + * + * Configurations with different names are stored independently. + */ +- (void) setName:(NSString*)name; + +/** + * Writes the configuration to the backing store. + * + * The location the configuration is written to is determined by the + * [-name] property. + * + * @result YES if the configuration was successfully written, NO otherwise + */ +- (BOOL) writeToStore; +/** + * Writes the configuration to the backing store. + * + * The location the configuration is written to is determined by the + * [-name] property. + * + * @param error this will be set if the configuration could not be written + * to the backing store + * @result YES if the configuration was successfully written, NO otherwise + */ +- (BOOL) writeToStoreWithError:(NSError**)error; + +/** + * Check whether a particular key exists within a group + * + * @param key the key to check for + * @param group the name of the group to look in + * @result YES if the key exists, NO otherwise + */ +- (BOOL) hasKey:(NSString*)key inGroup:(NSString*)group; +/** + * Check whether a particular group exists + * + * @param group the name of the group to check for + * @result YES if the group exists, NO otherwise + */ +- (BOOL) hasGroup:(NSString*)group; +/** + * List the groups in the configuration. + * + * @result a list of group names + */ +- (NSArray*) groups; + +/** + * Get the value associated with a key as a string + * + * This is only guaranteed to work if the value was stored as a string. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a string, or nil + * if no string value was associated with key + */ +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a string + * + * This is only guaranteed to work if the value was stored as a string. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no string value was associated with key + * @result the value associated with key as a string, or default + */ +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSString*)def; +/** + * Get the value associated with a key as a boolean + * + * This is only guaranteed to work if the value was stored as a boolean. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a boolean, or NO + * if no boolean value was associated with key + */ +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a boolean + * + * This is only guaranteed to work if the value was stored as a boolean. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no boolean value was associated with key + * @result the value associated with key as a boolean, or def + */ +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group withDefault:(BOOL)def; +/** + * Get the value associated with a key as a integer + * + * This is only guaranteed to work if the value was stored as a integer. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a integer, or 0 + * if no integer value was associated with key + */ +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a integer + * + * This is only guaranteed to work if the value was stored as a integer. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no integer value was associated with key + * @result the value associated with key as a integer, or def + */ +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group withDefault:(int)def; +/** + * Get the value associated with a key as a double + * + * This is only guaranteed to work if the value was stored as a double. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a double, or 0 + * if no double value was associated with key + */ +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a double + * + * This is only guaranteed to work if the value was stored as a double. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no double value was associated with key + * @result the value associated with key as a double, or def + */ +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group withDefault:(double)def; + +/** + * Get the value associated with a key as a list of strings + * + * This is only guaranteed to work if the value was stored as a + * list of strings. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of strings, + * or nil if no list of strings was associated with key + */ +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of strings + * + * This is only guaranteed to work if the value was stored as a + * list of strings. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no string list value was associated with key + * @result the value associated with key as a list of strings, or def + */ +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of booleans + * + * This is only guaranteed to work if the value was stored as a + * list of booleans. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing boolean values, or nil + */ +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of booleans + * + * This is only guaranteed to work if the value was stored as a + * list of booleans. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no boolean list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing boolean values, or def + */ +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of integers + * + * This is only guaranteed to work if the value was stored as a + * list of integers. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing integer values, or nil + */ +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of integers + * + * This is only guaranteed to work if the value was stored as a + * list of integers. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no integer list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing integer values, or def + */ +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; +/** + * Get the value associated with a key as a list of doubles + * + * This is only guaranteed to work if the value was stored as a + * list of doubles. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @result the value associated with key as a list of NSNumber + * objects, containing double values, or nil + */ +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group; +/** + * Get the value associated with a key as a list of doubles + * + * This is only guaranteed to work if the value was stored as a + * list of doubles. + * + * @param key the key to fetch the data for + * @param group the name of the group to look in + * @param def the value to return if no double list value was associated with key + * @result the value associated with key as a list of NSNumber + * objects, containing double values, or def + */ +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def; + +/** + * Associate a string value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setStringEntry:(NSString*)key inGroup:(NSString*)group value:(NSString*)value; +/** + * Associate a boolean value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setBooleanEntry:(NSString*)key inGroup:(NSString*)group value:(BOOL)value; +/** + * Associate a integer value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setIntegerEntry:(NSString*)key inGroup:(NSString*)group value:(int)value; +/** + * Associate a double value with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the value with + * @param group the group to store the association in + * @param value the value to store + */ +- (void) setDoubleEntry:(NSString*)key inGroup:(NSString*)group value:(double)value; + +/** + * Associate a list of string values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of strings + */ +- (void) setStringListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of boolean values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setBooleanListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of integer values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setIntegerListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +/** + * Associate a list of double values with a key. + * + * Any previous value (of any type) with the same key and group will + * be overwritten. + * + * @param key the key to associate the list with + * @param group the group to store the association in + * @param value the list to store, as an array of NSNumber objects + */ +- (void) setDoubleListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; + +/** + * Remove a group from the configuration + * + * This will remove all the groups key-value associations. + */ +- (void) removeGroup:(NSString*)group; +/** + * Remove a key from the configuration + * + * @param key the key to remove + * @param group the group to remove it from + */ +- (void) removeKey:(NSString*)key inGroup:(NSString*)group; + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/Configuration.m b/tikzit/src/linux/Configuration.m new file mode 100644 index 0000000..4904eed --- /dev/null +++ b/tikzit/src/linux/Configuration.m @@ -0,0 +1,446 @@ +// +// 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 "Configuration.h" +#import "SupportDir.h" + +@implementation Configuration + ++ (NSString*) _pathFromName:(NSString*)name { + return [NSString stringWithFormat:@"%@/%@.conf", [SupportDir userSupportDir], name]; +} + ++ (BOOL) configurationExistsWithName:(NSString*)name { + BOOL isDir; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:[self _pathFromName:name] isDirectory:&isDir]; + return exists && !isDir; +} + ++ (Configuration*) emptyConfigurationWithName:(NSString*)name + { return [[[self alloc] initEmptyWithName:name] autorelease]; } ++ (Configuration*) configurationWithName:(NSString*)name + { return [[[self alloc] initWithName:name] autorelease]; } ++ (Configuration*) configurationWithName:(NSString*)name loadError:(NSError**)error + { return [[[self alloc] initWithName:name loadError:error] autorelease]; } + +- (id) init +{ + [self release]; + return nil; +} + +- (id) initEmptyWithName:(NSString*)n +{ + self = [super init]; + if (self) { + name = [n retain]; + file = g_key_file_new (); + } + + return self; +} + +- (id) _initFromFile:(NSString*)path error:(NSError**)error +{ + self = [super init]; + if (self) { + file = g_key_file_new (); + + NSFileManager *manager = [NSFileManager defaultManager]; + if ([manager fileExistsAtPath:path]) { + gchar *filename = [path glibFilename]; + + GError *gerror = NULL; + g_key_file_load_from_file (file, + filename, + G_KEY_FILE_NONE, + &gerror); + g_free (filename); + + if (gerror) { + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + } + } + + return self; +} + +- (id) initWithName:(NSString*)n { + return [self initWithName:n loadError:NULL]; +} + +- (id) initWithName:(NSString*)n loadError:(NSError**)error { + self = [self _initFromFile:[Configuration _pathFromName:n] error:error]; + + if (self) { + name = [n retain]; + } + + return self; +} + +- (BOOL) _ensureParentExists:(NSString*)path error:(NSError**)error { + NSString *directory = [path stringByDeletingLastPathComponent]; + return [[NSFileManager defaultManager] ensureDirectoryExists:directory error:error]; +} + +- (BOOL) _writeFileTo:(NSString*)path error:(NSError**)error +{ + if (![self _ensureParentExists:path error:error]) { + return NO; + } + + BOOL success = NO; + gsize length; + gchar *data = g_key_file_to_data (file, &length, NULL); // never reports an error + if (data && length) { + GError *gerror = NULL; + gchar* nativePath = [path glibFilename]; + success = g_file_set_contents (nativePath, data, length, &gerror) ? YES : NO; + g_free (data); + g_free (nativePath); + if (gerror) { + g_warning ("Failed to write file: %s\n", gerror->message); + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + } else { + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; + success = YES; + } + + return success; +} + +- (NSString*) name { + return name; +} + +- (void) setName:(NSString*)n { + [n retain]; + [name release]; + name = n; +} + +- (BOOL) writeToStore { + return [self writeToStoreWithError:NULL]; +} + +- (BOOL) writeToStoreWithError:(NSError**)error { + return [self _writeFileTo:[Configuration _pathFromName:name] error:error]; +} + +- (BOOL) hasKey:(NSString*)key inGroup:(NSString*)group +{ + gboolean result = g_key_file_has_key (file, [group UTF8String], [key UTF8String], NULL); + return result ? YES : NO; +} + +- (BOOL) hasGroup:(NSString*)group +{ + gboolean result = g_key_file_has_group (file, [group UTF8String]); + return result ? YES : NO; +} + +- (NSArray*) keys:(NSString*)group +{ + gsize length; + gchar **keys = g_key_file_get_keys (file, [group UTF8String], &length, NULL); + if (!keys) + length = 0; + + NSMutableArray *array = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [array addObject:[NSString stringWithUTF8String:keys[i]]]; + } + g_strfreev (keys); + return array; +} + +- (NSArray*) groups +{ + gsize length; + gchar **groups = g_key_file_get_groups (file, &length); + NSMutableArray *array = [NSMutableArray arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [array addObject:[NSString stringWithUTF8String:groups[i]]]; + } + g_strfreev (groups); + return array; +} + +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self stringEntry:key inGroup:group withDefault:nil]; +} + +- (NSString*) stringEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSString*)def +{ + NSString *result = def; + gchar *entry = g_key_file_get_string (file, [group UTF8String], [key UTF8String], NULL); + if (entry) { + result = [NSString stringWithUTF8String:entry]; + g_free (entry); + } + return result; +} + +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group withDefault:(BOOL)def +{ + GError *error = NULL; + gboolean result = g_key_file_get_boolean (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result ? YES : NO; + } +} + +- (BOOL) booleanEntry:(NSString*)key inGroup:(NSString*)group +{ + gboolean result = g_key_file_get_boolean (file, [group UTF8String], [key UTF8String], NULL); + return result ? YES : NO; +} + +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group withDefault:(int)def +{ + GError *error = NULL; + int result = g_key_file_get_integer (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result; + } +} + +- (int) integerEntry:(NSString*)key inGroup:(NSString*)group +{ + return g_key_file_get_integer (file, [group UTF8String], [key UTF8String], NULL); +} + +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group withDefault:(double)def +{ + GError *error = NULL; + double result = g_key_file_get_double (file, [group UTF8String], [key UTF8String], &error); + if (error != NULL) { + g_error_free (error); + return def; + } else { + return result; + } +} + +- (double) doubleEntry:(NSString*)key inGroup:(NSString*)group +{ + return g_key_file_get_double (file, [group UTF8String], [key UTF8String], NULL); +} + +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self stringListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) stringListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gchar **list = g_key_file_get_string_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray new]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSString stringWithUTF8String:list[i]]]; + } + return result; + } else { + return def; + } +} + +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self booleanListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) booleanListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gboolean *list = g_key_file_get_boolean_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray new]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithBool:list[i]]]; + } + return result; + } else { + return def; + } +} + +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self integerListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) integerListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + gint *list = g_key_file_get_integer_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray new]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithInt:list[i]]]; + } + return result; + } else { + return def; + } +} + +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group +{ + return [self doubleListEntry:key inGroup:group withDefault:nil]; +} + +- (NSArray*) doubleListEntry:(NSString*)key inGroup:(NSString*)group withDefault:(NSArray*)def +{ + gsize length; + double *list = g_key_file_get_double_list (file, [group UTF8String], [key UTF8String], &length, NULL); + if (list) { + NSMutableArray *result = [NSMutableArray new]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithDouble:list[i]]]; + } + return result; + } else { + return def; + } +} + +- (void) setStringEntry:(NSString*)key inGroup:(NSString*)group value:(NSString*)value +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + g_key_file_set_string (file, [group UTF8String], [key UTF8String], [value UTF8String]); +} + +- (void) setBooleanEntry:(NSString*)key inGroup:(NSString*)group value:(BOOL)value; +{ + g_key_file_set_boolean (file, [group UTF8String], [key UTF8String], value); +} + +- (void) setIntegerEntry:(NSString*)key inGroup:(NSString*)group value:(int)value; +{ + g_key_file_set_integer (file, [group UTF8String], [key UTF8String], value); +} + +- (void) setDoubleEntry:(NSString*)key inGroup:(NSString*)group value:(double)value; +{ + g_key_file_set_double (file, [group UTF8String], [key UTF8String], value); +} + + +- (void) setStringListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + const gchar * *list = g_new (const gchar *, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] UTF8String]; + } + g_key_file_set_string_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setBooleanListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gboolean *list = g_new (gboolean, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] boolValue]; + } + g_key_file_set_boolean_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setIntegerListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gint *list = g_new (gint, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] intValue]; + } + g_key_file_set_integer_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) setDoubleListEntry:(NSString*)key inGroup:(NSString*)group value:(NSArray*)value; +{ + if (value == nil) { + [self removeKey:key inGroup:group]; + return; + } + gsize length = [value count]; + gdouble *list = g_new (gdouble, length); + for (int i = 0; i < length; ++i) { + list[i] = [[value objectAtIndex:i] doubleValue]; + } + g_key_file_set_double_list (file, [group UTF8String], [key UTF8String], list, length); + g_free (list); +} + +- (void) removeGroup:(NSString*)group +{ + g_key_file_remove_group (file, [group UTF8String], NULL); +} + +- (void) removeKey:(NSString*)key inGroup:(NSString*)group; +{ + g_key_file_remove_key (file, [group UTF8String], [key UTF8String], NULL); +} + +- (void) dealloc +{ + [name release]; + g_key_file_free (file); + file = NULL; + [super dealloc]; +} + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/Edge+Render.h b/tikzit/src/linux/Edge+Render.h new file mode 100644 index 0000000..11f02b1 --- /dev/null +++ b/tikzit/src/linux/Edge+Render.h @@ -0,0 +1,33 @@ +/* + * 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 "TZFoundation.h" +#import "Edge.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface Edge (Render) + ++ (float) controlPointRadius; +- (void) renderControlsToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context; +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context selected:(BOOL)selected; +- (NSRect) renderedBoundsWithTransformer:(Transformer*)t whenSelected:(BOOL)selected; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface withFuzz:(float)fuzz; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Edge+Render.m b/tikzit/src/linux/Edge+Render.m new file mode 100644 index 0000000..2e5f127 --- /dev/null +++ b/tikzit/src/linux/Edge+Render.m @@ -0,0 +1,208 @@ +/* + * 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 "Edge+Render.h" +#import "Node+Render.h" +#import "../common/util.h" + +static const float edgeWidth = 2.0; +static const float cpRadius = 3.0; +static const float cpLineWidth = 1.0; + +@implementation Edge (Render) + ++ (float) controlPointRadius { + return cpRadius; +} + +- (float) controlDistanceWithTransformer:(Transformer*)transformer { + NSPoint c_source = [transformer toScreen:src]; + NSPoint c_target = [transformer toScreen:targ]; + const float dx = (c_target.x - c_source.x); + const float dy = (c_target.y - c_source.y); + if (dx == 0 && dy == 0) { + return [transformer scaleToScreen:weight]; + } else { + return NSDistanceBetweenPoints(c_source, c_target) * weight; + } +} + +- (void) renderControlsToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context { + Transformer *transformer = [surface transformer]; + + [context saveState]; + + [self updateControls]; + + NSPoint c_source = [transformer toScreen:src]; + NSPoint c_target = [transformer toScreen:targ]; + NSPoint c_mid = [transformer toScreen:mid]; + + const float dx = (c_target.x - c_source.x); + const float dy = (c_target.y - c_source.y); + + [context setLineWidth:cpLineWidth]; + RColor fillColor = MakeRColor (1.0, 1.0, 1.0, 0.5); + + // draw a circle at the mid point + [context startPath]; + [context circleAt:c_mid withRadius:cpRadius]; + [context strokePathWithColor:MakeSolidRColor(0, 0, 1) andFillWithColor:fillColor]; + + //[context setAntialiasMode:AntialiasDisabled]; + + // size of control circles + float c_dist = 0.0f; + if (dx == 0 && dy == 0) { + c_dist = [transformer scaleToScreen:weight]; + } else { + c_dist = NSDistanceBetweenPoints(c_source, c_target) * weight; + } + + // basic bend is blue, in-out is green + RColor controlTrackColor; + if ([self bendMode] == EdgeBendModeBasic) { + controlTrackColor = MakeRColor (0.0, 0.0, 1.0, 0.4); + } else { + controlTrackColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + + [context startPath]; + [context circleAt:c_source withRadius:c_dist]; + if (dx != 0 || dy != 0) { + [context circleAt:c_target withRadius:c_dist]; + } + [context strokePathWithColor:controlTrackColor]; + + RColor handleColor = MakeRColor (1.0, 0.0, 1.0, 0.6); + if ([self bendMode] == EdgeBendModeBasic) { + if (bend % 45 != 0) { + handleColor = MakeRColor (0.0, 0.0, 0.1, 0.4); + } + } else if ([self bendMode] == EdgeBendModeInOut) { + if (outAngle % 45 != 0) { + handleColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + } + + NSPoint c_cp1 = [transformer toScreen:cp1]; + [context moveTo:c_source]; + [context lineTo:c_cp1]; + [context circleAt:c_cp1 withRadius:cpRadius]; + [context strokePathWithColor:handleColor]; + + if ([self bendMode] == EdgeBendModeInOut) { + // recalculate color based on inAngle + if (inAngle % 45 == 0) { + handleColor = MakeRColor (1.0, 0.0, 1.0, 0.6); + } else { + handleColor = MakeRColor (0.0, 0.7, 0.0, 0.4); + } + } + + NSPoint c_cp2 = [transformer toScreen:cp2]; + [context moveTo:c_target]; + [context lineTo:c_cp2]; + [context circleAt:c_cp2 withRadius:cpRadius]; + [context strokePathWithColor:handleColor]; + + [context restoreState]; +} + +- (void) createStrokePathInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer { + NSPoint c_source = [transformer toScreen:src]; + NSPoint c_cp1 = [transformer toScreen:cp1]; + NSPoint c_cp2 = [transformer toScreen:cp2]; + NSPoint c_target = [transformer toScreen:targ]; + + [context startPath]; + [context moveTo:c_source]; + [context curveTo:c_target withCp1:c_cp1 andCp2:c_cp2]; + + if ([self style] != nil) { + // draw edge decoration + switch ([[self style] decorationStyle]) { + case ED_None: + break; + case ED_Tick: + [context moveTo:[transformer toScreen:[self leftNormal]]]; + [context lineTo:[transformer toScreen:[self rightNormal]]]; + break; + case ED_Arrow: + [context moveTo:[transformer toScreen:[self leftNormal]]]; + [context lineTo:[transformer toScreen:[self midTan]]]; + [context lineTo:[transformer toScreen:[self rightNormal]]]; + break; + } + } + +} + +- (void) renderToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context selected:(BOOL)selected { + [self updateControls]; + + [context saveState]; + [context setLineWidth:edgeWidth]; + [self createStrokePathInContext:context withTransformer:[surface transformer]]; + RColor color = BlackRColor; + if (selected) { + color.alpha = 0.5; + } + [context strokePathWithColor:color]; + [context restoreState]; + + if (selected) { + [self renderControlsToSurface:surface withContext:context]; + } + + if ([self hasEdgeNode]) { + NSPoint labelPt = [[surface transformer] toScreen:[self mid]]; + [[self edgeNode] renderLabelAt:labelPt + withContext:context]; + } +} + +- (NSRect) renderedBoundsWithTransformer:(Transformer*)t whenSelected:(BOOL)selected { + NSRect bRect = [t rectToScreen:[self boundingRect]]; + if (selected) { + float c_dist = [self controlDistanceWithTransformer:t]; + return NSInsetRect (bRect, -c_dist, -c_dist); + } else { + return bRect; + } +} + +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface withFuzz:(float)fuzz { + [self updateControls]; + + NSRect boundingRect = [[surface transformer] rectToScreen:[self boundingRect]]; + if (!NSPointInRect(p, NSInsetRect(boundingRect, -fuzz, -fuzz))) { + return NO; + } + + id<RenderContext> cr = [surface createRenderContext]; + + [cr setLineWidth:edgeWidth + 2 * fuzz]; + [self createStrokePathInContext:cr withTransformer:[surface transformer]]; + + return [cr strokeIncludesPoint:p]; +} + +@end + +// vim:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit/src/linux/EdgeStyle+Storage.h b/tikzit/src/linux/EdgeStyle+Storage.h new file mode 100644 index 0000000..74881f3 --- /dev/null +++ b/tikzit/src/linux/EdgeStyle+Storage.h @@ -0,0 +1,29 @@ +/* + * 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 "TZFoundation.h" +#import "EdgeStyle.h" +#import "Configuration.h" + +@interface EdgeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/EdgeStyle+Storage.m b/tikzit/src/linux/EdgeStyle+Storage.m new file mode 100644 index 0000000..0904e1c --- /dev/null +++ b/tikzit/src/linux/EdgeStyle+Storage.m @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "EdgeStyle+Storage.h" +#import "ColorRGB+IntegerListStorage.h" + +@implementation EdgeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + self = [self init]; + + if (self) { + [self setName:[configFile stringEntry:@"Name" inGroup:groupName withDefault:name]]; + [self setCategory:[configFile stringEntry:@"Category" inGroup:groupName withDefault:category]]; + headStyle = [configFile integerEntry:@"HeadStyle" inGroup:groupName withDefault:headStyle]; + tailStyle = [configFile integerEntry:@"TailStyle" inGroup:groupName withDefault:tailStyle]; + decorationStyle = [configFile integerEntry:@"DecorationStyle" inGroup:groupName withDefault:decorationStyle]; + thickness = [configFile doubleEntry:@"Thickness" inGroup:groupName withDefault:thickness]; + } + + return self; +} + +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + [configFile setStringEntry:@"Name" inGroup:groupName value:name]; + [configFile setStringEntry:@"Category" inGroup:groupName value:category]; + [configFile setIntegerEntry:@"HeadStyle" inGroup:groupName value:headStyle]; + [configFile setIntegerEntry:@"TailStyle" inGroup:groupName value:tailStyle]; + [configFile setIntegerEntry:@"DecorationStyle" inGroup:groupName value:decorationStyle]; + [configFile setDoubleEntry:@"Thickness" inGroup:groupName value:thickness]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/FileChooserDialog.h b/tikzit/src/linux/FileChooserDialog.h new file mode 100644 index 0000000..80b03f5 --- /dev/null +++ b/tikzit/src/linux/FileChooserDialog.h @@ -0,0 +1,56 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +@interface FileChooserDialog: NSObject { + GtkFileChooser *dialog; +} + ++ (FileChooserDialog*) saveDialog; ++ (FileChooserDialog*) saveDialogWithParent:(GtkWindow*)parent; ++ (FileChooserDialog*) saveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; ++ (FileChooserDialog*) openDialog; ++ (FileChooserDialog*) openDialogWithParent:(GtkWindow*)parent; ++ (FileChooserDialog*) openDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; + +- (id) initSaveDialog; +- (id) initSaveDialogWithParent:(GtkWindow*)parent; +- (id) initSaveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; +- (id) initOpenDialog; +- (id) initOpenDialogWithParent:(GtkWindow*)parent; +- (id) initOpenDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent; + +- (void) addStandardFilters; +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern; +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern setSelected:(BOOL)selected; + +- (void) setCurrentFolder:(NSString*)path; +- (NSString*) currentFolder; + +- (void) setSuggestedName:(NSString*)fileName; + +- (NSString*) filePath; + +- (BOOL) showDialog; + +- (void) destroy; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/FileChooserDialog.m b/tikzit/src/linux/FileChooserDialog.m new file mode 100644 index 0000000..9498e4c --- /dev/null +++ b/tikzit/src/linux/FileChooserDialog.m @@ -0,0 +1,152 @@ +/* + * 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 "FileChooserDialog.h" + +@implementation FileChooserDialog: NSObject + ++ (FileChooserDialog*) saveDialog { return [[[self alloc] initSaveDialog] autorelease]; } ++ (FileChooserDialog*) saveDialogWithParent:(GtkWindow*)parent + { return [[[self alloc] initSaveDialogWithParent:parent] autorelease]; } ++ (FileChooserDialog*) saveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent + { return [[[self alloc] initSaveDialogWithTitle:title parent:parent] autorelease]; } ++ (FileChooserDialog*) openDialog { return [[[self alloc] initOpenDialog] autorelease]; } ++ (FileChooserDialog*) openDialogWithParent:(GtkWindow*)parent + { return [[[self alloc] initOpenDialogWithParent:parent] autorelease]; } ++ (FileChooserDialog*) openDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent + { return [[[self alloc] initOpenDialogWithTitle:title parent:parent] autorelease]; } + +- (id) initSaveDialog { return [self initSaveDialogWithParent:NULL]; } +- (id) initSaveDialogWithParent:(GtkWindow*)parent + { return [self initSaveDialogWithTitle:@"Save file" parent:parent]; } +- (id) initSaveDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent { + self = [super init]; + + if (self) { + dialog = GTK_FILE_CHOOSER (gtk_file_chooser_dialog_new ( + [title UTF8String], + parent, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL)); + gtk_file_chooser_set_do_overwrite_confirmation (dialog, TRUE); + } + + return self; +} + +- (id) initOpenDialog { return [self initOpenDialogWithParent:NULL]; } +- (id) initOpenDialogWithParent:(GtkWindow*)parent + { return [self initOpenDialogWithTitle:@"Open file" parent:parent]; } +- (id) initOpenDialogWithTitle:(NSString*)title parent:(GtkWindow*)parent { + self = [super init]; + + if (self) { + dialog = GTK_FILE_CHOOSER (gtk_file_chooser_dialog_new ( + [title UTF8String], + parent, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL)); + } + + return self; +} + +- (void) addStandardFilters { + GtkFileFilter *tikzfilter = gtk_file_filter_new(); + gtk_file_filter_set_name(tikzfilter, ".tikz files"); + gtk_file_filter_add_pattern(tikzfilter, "*.tikz"); + gtk_file_chooser_add_filter(dialog, tikzfilter); + GtkFileFilter *allfilter = gtk_file_filter_new(); + gtk_file_filter_set_name(allfilter, "all files"); + gtk_file_filter_add_pattern(allfilter, "*"); + gtk_file_chooser_add_filter(dialog, allfilter); + gtk_file_chooser_set_filter(dialog, tikzfilter); +} + +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern { + [self addFileFilter:filterName withPattern:filePattern setSelected:NO]; +} + +- (void) addFileFilter:(NSString*)filterName withPattern:(NSString*)filePattern setSelected:(BOOL)selected { + GtkFileFilter *oldFilter = selected ? NULL : gtk_file_chooser_get_filter (dialog); + GtkFileFilter *filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, [filterName UTF8String]); + gtk_file_filter_add_pattern(filter, [filePattern UTF8String]); + gtk_file_chooser_add_filter(dialog, filter); + if (selected) { + gtk_file_chooser_set_filter (dialog, filter); + } else if (oldFilter) { + gtk_file_chooser_set_filter (dialog, oldFilter); + } +} + +- (void) setCurrentFolder:(NSString*)path { + gchar *folder = [path glibFilename]; + if (folder) { + gtk_file_chooser_set_current_folder(dialog, folder); + g_free (folder); + } +} + +- (NSString*) currentFolder { + NSString *path = nil; + gchar *folder = gtk_file_chooser_get_current_folder(dialog); + if (folder) { + path = [NSString stringWithGlibFilename:folder]; + g_free (folder); + } + return path; +} + +- (void) setSuggestedName:(NSString*)fileName { + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), [fileName UTF8String]); +} + +- (NSString*) filePath { + NSString *path = nil; + gchar *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + if (filename) { + path = [NSString stringWithGlibFilename:filename]; + g_free (filename); + } + return path; +} + +- (BOOL) showDialog { + return (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) ? YES : NO; +} + +- (void) destroy { + gtk_widget_destroy (GTK_WIDGET (dialog)); + dialog = NULL; +} + +- (void) dealloc { + if (dialog) { + g_warning ("Failed to destroy file chooser dialog!\n"); + gtk_widget_destroy (GTK_WIDGET (dialog)); + } + [super dealloc]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/GraphInputHandler.h b/tikzit/src/linux/GraphInputHandler.h new file mode 100644 index 0000000..61af36f --- /dev/null +++ b/tikzit/src/linux/GraphInputHandler.h @@ -0,0 +1,69 @@ +/* + * 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 "TZFoundation.h" +#import "GraphRenderer.h" +#import "InputDelegate.h" +#import "StyleManager.h" + +typedef enum { + SelectMode, + CreateNodeMode, + DrawEdgeMode, + BoundingBoxMode, + HandMode +} InputMode; + +typedef enum { + QuietState, + SelectBoxState, + ToggleSelectState, + MoveSelectedNodesState, + DragEdgeControlPoint1, + DragEdgeControlPoint2, + EdgeDragState, + BoundingBoxState, + CanvasDragState +} MouseState; + +@interface GraphInputHandler: NSObject { + GraphRenderer *renderer; + InputMode mode; + MouseState state; + float edgeFuzz; + NSPoint dragOrigin; + Node *leaderNode; + NSPoint oldLeaderPos; + Edge *modifyEdge; + NSMutableSet *selectionBoxContents; + ResizeHandle grabbedResizeHandle; + NSPoint oldOrigin; +} + +- (id) initWithGraphRenderer:(GraphRenderer*)r; + +- (float) edgeFuzz; +- (void) setEdgeFuzz:(float)fuzz; + +- (InputMode) mode; +- (void) setMode:(InputMode)mode; + +- (void) resetState; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/GraphInputHandler.m b/tikzit/src/linux/GraphInputHandler.m new file mode 100644 index 0000000..4d77045 --- /dev/null +++ b/tikzit/src/linux/GraphInputHandler.m @@ -0,0 +1,410 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * 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 "GraphInputHandler.h" +#import <gdk/gdkkeysyms.h> +#import "Edge+Render.h" + +static const InputMask unionSelectMask = ShiftMask; + +@implementation GraphInputHandler +- (id) initWithGraphRenderer:(GraphRenderer*)r { + self = [super init]; + + if (self) { + renderer = r; + mode = SelectMode; + state = QuietState; + edgeFuzz = 3.0f; + leaderNode = nil; + modifyEdge = nil; + selectionBoxContents = [[NSMutableSet alloc] initWithCapacity:10]; + grabbedResizeHandle = NoHandle; + } + + return self; +} + +- (TikzDocument*) doc { + return [renderer document]; +} + +- (void) deselectAllNodes { + [[[self doc] pickSupport] deselectAllNodes]; +} + +- (void) deselectAllEdges { + [[[self doc] pickSupport] deselectAllEdges]; +} + +- (void) deselectAll { + [[[self doc] pickSupport] deselectAllNodes]; + [[[self doc] pickSupport] deselectAllEdges]; +} + +- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to { + Transformer *transformer = [renderer transformer]; + + NSPoint from = [transformer toScreen:[leader point]]; + //to = [[renderer grid] snapScreenPoint:to]; + float dx = to.x - from.x; + float dy = to.y - from.y; + + for (Node *node in [[[self doc] pickSupport] selectedNodes]) { + NSPoint p = [transformer toScreen:[node point]]; + p.x += dx; + p.y += dy; + p = [[renderer grid] snapScreenPoint:p]; + [node setPoint:[transformer fromScreen:p]]; + } +} + +- (float) edgeFuzz { + return edgeFuzz; +} + +- (void) setEdgeFuzz:(float)fuzz { + edgeFuzz = fuzz; +} + +- (InputMode) mode { + return mode; +} + +- (void) resetState { + state = QuietState; +} + +- (void) setMode:(InputMode)m { + if (mode != m) { + if (mode == BoundingBoxMode) { + [renderer setBoundingBoxHandlesShown:NO]; + } + mode = m; + [self deselectAll]; + if (m == BoundingBoxMode) { + [renderer setBoundingBoxHandlesShown:YES]; + } + } +} + +- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p { + return (NSDistanceBetweenPoints(c, p) <= r); +} + +- (void) lookForControlPointAt:(NSPoint)pos { + const float cpr = [Edge controlPointRadius]; + for (Edge *e in [[[self doc] pickSupport] selectedEdges]) { + NSPoint cp1 = [[renderer transformer] toScreen:[e cp1]]; + if ([self circleWithCenter:cp1 andRadius:cpr containsPoint:pos]) { + state = DragEdgeControlPoint1; + modifyEdge = e; + [[self doc] startModifyEdge:e]; + return; + } + NSPoint cp2 = [[renderer transformer] toScreen:[e cp2]]; + if ([self circleWithCenter:cp2 andRadius:cpr containsPoint:pos]) { + state = DragEdgeControlPoint2; + modifyEdge = e; + [[self doc] startModifyEdge:e]; + return; + } + } +} + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + dragOrigin = pos; + + // we should already be in a quiet state, but no harm in making sure + state = QuietState; + + if (mode == HandMode || mask == ControlMask) { + state = CanvasDragState; + oldOrigin = [[renderer transformer] origin]; + } else if (mode == DrawEdgeMode) { + leaderNode = [renderer anyNodeAt:pos]; + if (leaderNode != nil) { + state = EdgeDragState; + } + } else if (mode == BoundingBoxMode) { + state = BoundingBoxState; + grabbedResizeHandle = [renderer boundingBoxResizeHandleAt:pos]; + [[self doc] startChangeBoundingBox]; + if (grabbedResizeHandle == NoHandle) { + [[[self doc] graph] setBoundingBox:NSZeroRect]; + [renderer setBoundingBoxHandlesShown:NO]; + } + } else if (mode == SelectMode) { + modifyEdge = nil; + [self lookForControlPointAt:pos]; + + if (modifyEdge == nil) { + // we didn't find a control point + + BOOL unionSelect = (mask & unionSelectMask); + + leaderNode = [renderer anyNodeAt:pos]; + // if we hit a node, deselect other nodes (if Shift is up) and go to move mode + if (leaderNode != nil) { + BOOL alreadySelected = [[self doc] isNodeSelected:leaderNode]; + if (!unionSelect && !alreadySelected) { + [self deselectAllEdges]; + [self deselectAllNodes]; + } + if (unionSelect && alreadySelected) { + state = ToggleSelectState; + } else { + [[[self doc] pickSupport] selectNode:leaderNode]; + state = MoveSelectedNodesState; + oldLeaderPos = [leaderNode point]; + [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]]; + } + } + + // if mouse did not hit a node, check if mouse hit an edge + if (leaderNode == nil) { + Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz]; + if (edge != nil) { + BOOL alreadySelected = [[self doc] isEdgeSelected:edge]; + if (!unionSelect) { + [self deselectAll]; + } + if (unionSelect && alreadySelected) { + [[[self doc] pickSupport] deselectEdge:edge]; + } else { + [[[self doc] pickSupport] selectEdge:edge]; + } + } else { + // if mouse did not hit anything, put us in box mode + if (!unionSelect) { + [self deselectAll]; + } + [selectionBoxContents removeAllObjects]; + state = SelectBoxState; + } + } + } + } +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (state == SelectBoxState) { + BOOL shouldDeselect = !(mask & unionSelectMask); + if (shouldDeselect) { + [self deselectAllEdges]; + } + [[[self doc] pickSupport] selectAllNodes:selectionBoxContents + replacingSelection:shouldDeselect]; + [renderer clearSelectionBox]; + } else if (state == ToggleSelectState) { + [[[self doc] pickSupport] deselectNode:leaderNode]; + leaderNode = nil; + } else if (state == MoveSelectedNodesState) { + if (NSEqualPoints (oldLeaderPos, [leaderNode point])) { + [[self doc] cancelShiftNodes]; + } else { + [[self doc] endShiftNodes]; + } + leaderNode = nil; + } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) { + // FIXME: check if there was any real change + [[self doc] endModifyEdge]; + } else if (state == EdgeDragState) { + [renderer clearHalfEdge]; + Node *targ = [renderer anyNodeAt:pos]; + if (targ != nil) { + [[self doc] addEdgeFrom:leaderNode to:targ]; + } + } else if (state == QuietState && mode == CreateNodeMode) { + Transformer *transformer = [renderer transformer]; + NSPoint nodePoint = [transformer fromScreen:[[renderer grid] snapScreenPoint:pos]]; + [[self doc] addNodeAt:nodePoint]; + } else if (state == BoundingBoxState) { + [[self doc] endChangeBoundingBox]; + [renderer setBoundingBoxHandlesShown:YES]; + } + + state = QuietState; +} + +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (mode != SelectMode) { + return; + } + if (state != QuietState) { + return; + } + // convert bend mode on edge under mouse cursor + Edge *edge = [renderer anyEdgeAt:pos withFuzz:edgeFuzz]; + if (edge != nil) { + [[self doc] startModifyEdge:edge]; + if ([edge bendMode]==EdgeBendModeBasic) { + [edge convertBendToAngles]; + [edge setBendMode:EdgeBendModeInOut]; + } else { + [edge setBendMode:EdgeBendModeBasic]; + } + [[self doc] endModifyEdge]; + + [self deselectAllEdges]; + [[[self doc] pickSupport] selectEdge:edge]; + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + Transformer *transformer = [renderer transformer]; + + if (state == ToggleSelectState) { + state = MoveSelectedNodesState; + oldLeaderPos = [leaderNode point]; + [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]]; + } + + if (state == SelectBoxState) { + NSRect selectionBox = NSRectAroundPoints(dragOrigin, pos); + [renderer setSelectionBox:selectionBox]; + + NSEnumerator *enumerator = [[self doc] nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + NSPoint nodePos = [transformer toScreen:[node point]]; + if (NSPointInRect(nodePos, selectionBox)) { + if (![selectionBoxContents member:node]) { + [selectionBoxContents addObject:node]; + [renderer invalidateNode:node]; + } + } else { + if ([selectionBoxContents member:node]) { + [selectionBoxContents removeObject:node]; + [renderer invalidateNode:node]; + } + } + } + } else if (state == MoveSelectedNodesState) { + if (leaderNode != nil) { + [self shiftNodesByMovingLeader:leaderNode to:pos]; + NSPoint shiftSoFar; + shiftSoFar.x = [leaderNode point].x - oldLeaderPos.x; + shiftSoFar.y = [leaderNode point].y - oldLeaderPos.y; + [[self doc] shiftNodesUpdate:shiftSoFar]; + } + } else if (state == DragEdgeControlPoint1 || state == DragEdgeControlPoint2) { + // invalidate once before we start changing it: we may be shrinking + // the control circles + [[self doc] modifyEdgeCheckPoint]; + if (state == DragEdgeControlPoint1) { + [modifyEdge moveCp1To:[transformer fromScreen:pos] + withWeightCourseness:0.1f + andBendCourseness:15 + forceLinkControlPoints:(mask & ControlMask)]; + } else { + [modifyEdge moveCp2To:[transformer fromScreen:pos] + withWeightCourseness:0.1f + andBendCourseness:15 + forceLinkControlPoints:(mask & ControlMask)]; + } + [[self doc] modifyEdgeCheckPoint]; + } else if (state == EdgeDragState) { + [renderer setHalfEdgeFrom:leaderNode to:pos]; + } else if (state == BoundingBoxState) { + Grid *grid = [renderer grid]; + Graph *graph = [[self doc] graph]; + if (grabbedResizeHandle == NoHandle) { + NSRect bbox = NSRectAroundPoints( + [grid snapScreenPoint:dragOrigin], + [grid snapScreenPoint:pos] + ); + [graph setBoundingBox:[transformer rectFromScreen:bbox]]; + } else { + NSRect bbox = [transformer rectToScreen:[graph boundingBox]]; + NSPoint p2 = [grid snapScreenPoint:pos]; + + if (grabbedResizeHandle == NorthWestHandle || + grabbedResizeHandle == NorthHandle || + grabbedResizeHandle == NorthEastHandle) { + + float dy = p2.y - NSMinY(bbox); + if (dy < bbox.size.height) { + bbox.origin.y += dy; + bbox.size.height -= dy; + } else { + bbox.origin.y = NSMaxY(bbox); + bbox.size.height = 0; + } + + } else if (grabbedResizeHandle == SouthWestHandle || + grabbedResizeHandle == SouthHandle || + grabbedResizeHandle == SouthEastHandle) { + + float dy = p2.y - NSMaxY(bbox); + if (-dy < bbox.size.height) { + bbox.size.height += dy; + } else { + bbox.size.height = 0; + } + } + + if (grabbedResizeHandle == NorthWestHandle || + grabbedResizeHandle == WestHandle || + grabbedResizeHandle == SouthWestHandle) { + + float dx = p2.x - NSMinX(bbox); + if (dx < bbox.size.width) { + bbox.origin.x += dx; + bbox.size.width -= dx; + } else { + bbox.origin.x = NSMaxX(bbox); + bbox.size.width = 0; + } + + } else if (grabbedResizeHandle == NorthEastHandle || + grabbedResizeHandle == EastHandle || + grabbedResizeHandle == SouthEastHandle) { + + float dx = p2.x - NSMaxX(bbox); + if (-dx < bbox.size.width) { + bbox.size.width += dx; + } else { + bbox.size.width = 0; + } + } + [graph setBoundingBox:[transformer rectFromScreen:bbox]]; + } + [[self doc] changeBoundingBoxCheckPoint]; + } else if (state == CanvasDragState) { + NSPoint newOrigin = oldOrigin; + newOrigin.x += pos.x - dragOrigin.x; + newOrigin.y += pos.y - dragOrigin.y; + [[renderer transformer] setOrigin:newOrigin]; + [renderer invalidateGraph]; + } +} + +- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask { + if (mask == ControlMask) { + if (dir == ScrollUp) { + [[renderer surface] zoomInAboutPoint:pos]; + } else if (dir == ScrollDown) { + [[renderer surface] zoomOutAboutPoint:pos]; + } + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/GraphRenderer.h b/tikzit/src/linux/GraphRenderer.h new file mode 100644 index 0000000..4609766 --- /dev/null +++ b/tikzit/src/linux/GraphRenderer.h @@ -0,0 +1,105 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +// classes +#import "Graph.h" +#import "Grid.h" +#import "PickSupport.h" +#import "TikzDocument.h" + +// protocols +#import "Surface.h" + +typedef enum { + NoHandle, + EastHandle, + SouthEastHandle, + SouthHandle, + SouthWestHandle, + WestHandle, + NorthWestHandle, + NorthHandle, + NorthEastHandle +} ResizeHandle; + +@interface GraphRenderer: NSObject <RenderDelegate> { + TikzDocument *doc; + NSObject<Surface> *surface; + Grid *grid; + NSRect selectionBox; + Node *halfEdgeOrigin; + NSPoint halfEdgeOriginPoint; + NSPoint halfEdgeEnd; + BOOL showBoundingBoxHandles; +} + +- (id) initWithSurface:(NSObject <Surface> *)surface; +- (id) initWithSurface:(NSObject <Surface> *)surface document:(TikzDocument*)document; +- (void) renderWithContext:(id<RenderContext>)context; +- (void) invalidateGraph; +- (void) invalidateNode:(Node*)node; +- (void) invalidateEdge:(Edge*)edge; +- (void) invalidateNodesHitBy:(NSPoint)point; +- (BOOL) point:(NSPoint)p hitsNode:(Node*)node; +- (BOOL) point:(NSPoint)p hitsEdge:(Edge*)edge withFuzz:(float)fuzz; +/** + * Finds a node at the given screen location. + * + * If there is more than one node at this point (because they overlap), + * an arbitrary one is returned. + */ +- (Node*) anyNodeAt:(NSPoint)p; +/** + * Finds an edge at the given screen location. + * + * If there is more than one edge at this point (because they overlap), + * an arbitrary one is returned. + * + * @param fuzz the fuzz for detecting edges: this will pick up + * edges that are close to the point + */ +- (Edge*) anyEdgeAt:(NSPoint)p withFuzz:(float)fuzz; + +- (id<Surface>) surface; +- (Transformer*) transformer; +- (Grid*) grid; +- (PickSupport*) pickSupport; + +- (Graph*) graph; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)document; + +- (NSRect) selectionBox; +- (void) setSelectionBox:(NSRect)box; +- (void) clearSelectionBox; + +- (void) setHalfEdgeFrom:(Node*)origin to:(NSPoint)end; +- (void) clearHalfEdge; + +- (BOOL) boundingBoxHandlesShown; +- (void) setBoundingBoxHandlesShown:(BOOL)shown; + +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)point; +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/GraphRenderer.m b/tikzit/src/linux/GraphRenderer.m new file mode 100644 index 0000000..c964f1b --- /dev/null +++ b/tikzit/src/linux/GraphRenderer.m @@ -0,0 +1,607 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * 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 "GraphRenderer.h" +#import "Edge+Render.h" +#import "Node+Render.h" + +static const float size = 5.0; + +float sideHandleTop(NSRect bbox) { + return (NSMinY(bbox) + NSMaxY(bbox) - size)/2.0f; +} + +float tbHandleLeft(NSRect bbox) { + return (NSMinX(bbox) + NSMaxX(bbox) - size)/2.0f; +} +void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); + +@interface GraphRenderer (Private) +- (BOOL) selectionBoxContainsNode:(Node*)node; +- (BOOL) halfEdgeIncludesNode:(Node*)node; +- (enum NodeState) nodeState:(Node*)node; +- (void) renderBoundingBoxWithContext:(id<RenderContext>)context; +- (void) renderSelectionBoxWithContext:(id<RenderContext>)context; +- (void) renderImpendingEdgeWithContext:(id<RenderContext>)context; +- (void) nodeNeedsRefreshing:(NSNotification*)notification; +- (void) edgeNeedsRefreshing:(NSNotification*)notification; +- (void) graphNeedsRefreshing:(NSNotification*)notification; +- (void) graphChanged:(NSNotification*)notification; +- (void) nodeStylePropertyChanged:(NSNotification*)notification; +- (void) edgeStylePropertyChanged:(NSNotification*)notification; +@end + +@implementation GraphRenderer + +- (id) initWithSurface:(NSObject <Surface> *)s { + self = [super init]; + + if (self) { + surface = [s retain]; + doc = nil; + grid = [[Grid alloc] initWithSpacing:1.0f subdivisions:4 transformer:[s transformer]]; + halfEdgeOrigin = nil; + [surface setRenderDelegate:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:nil]; + } + + return self; +} + +- (id) initWithSurface:(NSObject <Surface> *)s document:(TikzDocument*)document { + self = [self initWithSurface:s]; + + if (self) { + [self setDocument:document]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [doc release]; + [grid release]; + [surface release]; + + [super dealloc]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + [self renderWithContext:context]; +} + +- (void) renderWithContext:(id<RenderContext>)context { + // blank surface + [context paintWithColor:WhiteRColor]; + + // draw grid + [grid renderGridInContext:context]; + + // draw edges + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + [edge renderToSurface:surface withContext:context selected:[doc isEdgeSelected:edge]]; + } + + // draw nodes + enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + [node renderToSurface:surface withContext:context state:[self nodeState:node]]; + } + + [self renderBoundingBoxWithContext:context]; + [self renderSelectionBoxWithContext:context]; + [self renderImpendingEdgeWithContext:context]; +} + +- (void) invalidateGraph { + [surface invalidate]; +} + +- (void) invalidateNodes:(NSSet*)nodes { + for (Node *node in nodes) { + [self invalidateNode:node]; + } +} + +- (void) invalidateEdges:(NSSet*)edges { + for (Edge *edge in edges) { + [self invalidateEdge:edge]; + } +} + +- (void) invalidateNode:(Node*)node { + if (node == nil) { + return; + } + NSRect nodeRect = [node boundsWithLabelOnSurface:surface]; + nodeRect = NSInsetRect (nodeRect, -2.0f, -2.0f); + [surface invalidateRect:nodeRect]; +} + +- (void) invalidateEdge:(Edge*)edge { + if (edge == nil) { + return; + } + BOOL selected = [doc isEdgeSelected:edge]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:selected]; + edgeRect = NSInsetRect (edgeRect, -2.0f, -2.0f); + [surface invalidateRect:edgeRect]; +} + +- (void) invalidateNodesHitBy:(NSPoint)point { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:point hitsNode:node]) { + [self invalidateNode:node]; + } + } +} + +- (BOOL) point:(NSPoint)p hitsNode:(Node*)node { + return [node hitByPoint:p onSurface:surface]; +} + +- (BOOL) point:(NSPoint)p fuzzyHitsNode:(Node*)node { + NSRect bounds = [node boundsOnSurface:surface]; + return NSPointInRect(p, bounds); +} + +- (BOOL) point:(NSPoint)p hitsEdge:(Edge*)edge withFuzz:(float)fuzz { + return [edge hitByPoint:p onSurface:surface withFuzz:fuzz]; +} + +- (Node*) anyNodeAt:(NSPoint)p { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:p hitsNode:node]) { + return node; + } + } + return nil; +} + +- (Edge*) anyEdgeAt:(NSPoint)p withFuzz:(float)fuzz { + // FIXME: is there an efficient way to find the "nearest" edge + // if the fuzz is the reason we hit more than one? + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + if ([self point:p hitsEdge:edge withFuzz:fuzz]) { + return edge; + } + } + return nil; +} + +- (id<Surface>) surface { + return surface; +} + +- (Transformer*) transformer { + return [surface transformer]; +} + +- (Grid*) grid { + return grid; +} + +- (PickSupport*) pickSupport { + return [doc pickSupport]; +} + +- (Graph*) graph { + return [doc graph]; +} + +- (TikzDocument*) document { + return doc; +} + +- (void) setDocument:(TikzDocument*)document { + if (doc == document) { + return; + } + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:doc]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[doc pickSupport]]; + } + + [document retain]; + [doc release]; + doc = document; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"GraphReplaced" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphBeingChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChangeCancelled" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"NodeSelectionReplaced" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"EdgeSelectionReplaced" object:[doc pickSupport]]; + } + [surface invalidate]; +} + +- (NSRect) selectionBox { + return selectionBox; +} + +- (void) setSelectionBox:(NSRect)box { + NSRect invRect = NSUnionRect (selectionBox, box); + selectionBox = box; + [surface invalidateRect:NSInsetRect (invRect, -2, -2)]; +} + +- (void) clearSelectionBox { + NSRect oldRect = selectionBox; + + NSRect emptyRect; + selectionBox = emptyRect; + + [surface invalidateRect:NSInsetRect (oldRect, -2, -2)]; +} + +- (void) invalidateHalfEdge { + if (halfEdgeOrigin != nil) { + NSRect invRect = NSRectAroundPoints(halfEdgeEnd, halfEdgeOriginPoint); + invRect = NSUnionRect(invRect, [halfEdgeOrigin boundsWithLabelOnSurface:surface]); + + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:halfEdgeEnd fuzzyHitsNode:node]) { + invRect = NSUnionRect(invRect, [node boundsWithLabelOnSurface:surface]); + } + } + [surface invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)]; + } +} + +- (void) setHalfEdgeFrom:(Node*)origin to:(NSPoint)end { + [self invalidateHalfEdge]; + + if (halfEdgeOrigin != origin) { + [self invalidateNode:halfEdgeOrigin]; + halfEdgeOrigin = origin; + halfEdgeOriginPoint = [[surface transformer] toScreen:[origin point]]; + [self invalidateNode:origin]; + } + + if (origin != nil) { + halfEdgeEnd = end; + [self invalidateHalfEdge]; + } +} + +- (void) clearHalfEdge { + [self invalidateHalfEdge]; + halfEdgeOrigin = nil; +} + +- (BOOL) boundingBoxHandlesShown { + return showBoundingBoxHandles; +} + +- (void) setBoundingBoxHandlesShown:(BOOL)shown { + if (showBoundingBoxHandles != shown) { + showBoundingBoxHandles = shown; + [self invalidateGraph]; + } +} + +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p { + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + if (p.x >= NSMaxX(bbox)) { + if (p.x <= NSMaxX(bbox) + size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthEastHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthEastHandle; + } + } else { + float eastHandleTop = sideHandleTop(bbox); + if (p.y >= eastHandleTop && p.y <= (eastHandleTop + size)) { + return EastHandle; + } + } + } + } else if (p.x <= NSMinX(bbox)) { + if (p.x >= NSMinX(bbox) - size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthWestHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthWestHandle; + } + } else { + float westHandleTop = sideHandleTop(bbox); + if (p.y >= westHandleTop && p.y <= (westHandleTop + size)) { + return WestHandle; + } + } + } + } else if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthHandle; + } + } + return NoHandle; +} + +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle { + if (![[self graph] hasBoundingBox]) { + return NSZeroRect; + } + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + switch (handle) { + case EastHandle: + return NSMakeRect(NSMaxX(bbox), sideHandleTop(bbox), size, size); + case SouthEastHandle: + return NSMakeRect(NSMaxX(bbox), NSMaxY(bbox), size, size); + case SouthHandle: + return NSMakeRect(tbHandleLeft(bbox), NSMaxY(bbox), size, size); + case SouthWestHandle: + return NSMakeRect(NSMaxX(bbox), NSMinY(bbox) - size, size, size); + case WestHandle: + return NSMakeRect(NSMinX(bbox) - size, sideHandleTop(bbox), size, size); + case NorthWestHandle: + return NSMakeRect(NSMinX(bbox) - size, NSMinY(bbox) - size, size, size); + case NorthHandle: + return NSMakeRect(tbHandleLeft(bbox), NSMinY(bbox) - size, size, size); + case NorthEastHandle: + return NSMakeRect(NSMinX(bbox) - size, NSMaxY(bbox), size, size); + default: + return NSZeroRect; + } +} + +@end + +@implementation GraphRenderer (Private) +- (BOOL) selectionBoxContainsNode:(Node*)node { + return !NSIsEmptyRect (selectionBox) + && NSPointInRect([[surface transformer] toScreen:[node point]], selectionBox); +} +- (BOOL) halfEdgeIncludesNode:(Node*)node { + if (halfEdgeOrigin == nil) { + return FALSE; + } + return halfEdgeOrigin == node || [self point:halfEdgeEnd hitsNode:node]; +} +- (enum NodeState) nodeState:(Node*)node { + if ([doc isNodeSelected:node]) { + return NodeSelected; + } else if ([self selectionBoxContainsNode:node] || [self halfEdgeIncludesNode:node]) { + return NodeHighlighted; + } else { + return NodeNormal; + } +} + +- (void) renderBoundingBoxWithContext:(id<RenderContext>)context { + if ([[self graph] hasBoundingBox]) { + [context saveState]; + + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:bbox]; + [context strokePathWithColor:MakeSolidRColor (1.0, 0.7, 0.5)]; + + if ([self boundingBoxHandlesShown]) { + [context startPath]; + [context rect:[self boundingBoxResizeHandleRect:EastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthEastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:WestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthEastHandle]]; + [context strokePathWithColor:MakeSolidRColor (0.5, 0.5, 0.5)]; + } + + [context restoreState]; + } +} + +- (void) renderSelectionBoxWithContext:(id<RenderContext>)context { + if (!NSIsEmptyRect (selectionBox)) { + [context saveState]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:selectionBox]; + RColor fColor = MakeRColor (0.8, 0.8, 0.8, 0.2); + RColor sColor = MakeSolidRColor (0.6, 0.6, 0.6); + [context strokePathWithColor:sColor andFillWithColor:fColor]; + + [context restoreState]; + } +} + +- (void) renderImpendingEdgeWithContext:(id<RenderContext>)context { + if (halfEdgeOrigin == nil) { + return; + } + [context saveState]; + + [context setLineWidth:1.0]; + [context startPath]; + [context moveTo:halfEdgeOriginPoint]; + [context lineTo:halfEdgeEnd]; + [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)]; + + [context restoreState]; +} + +- (void) nodeNeedsRefreshing:(NSNotification*)notification { + [self invalidateNode:[[notification userInfo] objectForKey:@"node"]]; +} + +- (void) edgeNeedsRefreshing:(NSNotification*)notification { + Edge *edge = [[notification userInfo] objectForKey:@"edge"]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:YES]; + edgeRect = NSInsetRect (edgeRect, -2, -2); + [surface invalidateRect:edgeRect]; +} + +- (void) graphNeedsRefreshing:(NSNotification*)notification { + [self invalidateGraph]; +} + +- (void) graphChanged:(NSNotification*)notification { + GraphChange *change = [[notification userInfo] objectForKey:@"change"]; + switch ([change changeType]) { + case GraphAddition: + case GraphDeletion: + [self invalidateNodes:[change affectedNodes]]; + [self invalidateEdges:[change affectedEdges]]; + break; + case NodePropertyChange: + if (!NSEqualPoints ([[change oldNode] point], [[change nwNode] point])) { + // if the node has moved, it may be affecting edges + [surface invalidate]; + } else { + // invalide both old and new (old node may be larger) + [self invalidateNode:[change oldNode]]; + [self invalidateNode:[change nwNode]]; + } + break; + case EdgePropertyChange: + // invalide both old and new (old bend may increase bounds) + [self invalidateEdge:[change oldEdge]]; + [self invalidateEdge:[change nwEdge]]; + [self invalidateEdge:[change edgeRef]]; + break; + case NodesPropertyChange: + { + NSEnumerator *enumerator = [[change oldNodeTable] keyEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + NSPoint oldPos = [[[change oldNodeTable] objectForKey:node] point]; + NSPoint newPos = [[[change nwNodeTable] objectForKey:node] point]; + if (NSEqualPoints (oldPos, newPos)) { + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } else { + [surface invalidate]; + break; + } + } + } + break; + case NodesShift: + case NodesFlip: + case BoundingBoxChange: + [surface invalidate]; + break; + default: + // unknown change + [surface invalidate]; + break; + }; +} + +- (void) nodeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Node *node in [[self graph] nodes]) { + if ([node style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +- (void) edgeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Edge *edge in [[self graph] edges]) { + if ([edge style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/InputDelegate.h b/tikzit/src/linux/InputDelegate.h new file mode 100644 index 0000000..1e35223 --- /dev/null +++ b/tikzit/src/linux/InputDelegate.h @@ -0,0 +1,76 @@ +/* + * 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 "TZFoundation.h" + +typedef enum { + LeftButton = 1, + MiddleButton = 2, + RightButton = 3, + Button4 = 4, + Button5 = 5 +} MouseButton; + +typedef enum { + ShiftMask = 1, + ControlMask = 2, + MetaMask = 4 +} InputMask; + +typedef enum { + ScrollUp = 1, + ScrollDown = 2, + ScrollLeft = 3, + ScrollRight = 4, +} ScrollDirection; + +@interface NSObject (InputDelegate) +/** + * A mouse button was pressed. + */ +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * A mouse button was released. + */ +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * A mouse button was double-clicked. + * + * Note that mouseDown and mouseUp events will still be delivered. + * This will be triggered between the second mouseDown and the second + * mouseUp. + */ +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask; +/** + * The mouse was moved + */ +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)button andMask:(InputMask)mask; +/** + * The mouse was scrolled + */ +- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask; +/** + * A key was pressed + */ +- (void) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask; +/** + * A key was released + */ +- (void) keyReleased:(unsigned int)keyVal withMask:(InputMask)mask; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/MainWindow.h b/tikzit/src/linux/MainWindow.h new file mode 100644 index 0000000..8d3c583 --- /dev/null +++ b/tikzit/src/linux/MainWindow.h @@ -0,0 +1,215 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "WidgetSurface.h" + +@class Configuration; +@class GraphRenderer; +@class GraphInputHandler; +@class Menu; +@class StylesPalette; +@class StyleManager; +@class PropertyPane; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class TikzDocument; + +/** + * Manages the main application window + */ +@interface MainWindow: NSObject { + // the main application configuration + Configuration *configFile; + // maintains the known (user-defined) styles + StyleManager *styleManager; + // maintains the preambles used for previews + Preambles *preambles; + + // GTK+ widgets + GtkWindow *mainWindow; + GtkTextBuffer *tikzBuffer; + GtkStatusbar *statusBar; + GtkPaned *propsPane; + GtkPaned *stylesPane; + GtkPaned *graphPane; + GtkWidget *tikzDisp; + + // Classes that manage parts of the window + // (or other windows) + Menu *menu; + GraphRenderer *renderer; + GraphInputHandler *inputHandler; + StylesPalette *stylesPalette; + PropertyPane *propertyPane; + PreambleEditor *preambleWindow; + PreviewWindow *previewWindow; + + WidgetSurface *surface; + + // state variables + BOOL suppressTikzUpdates; + BOOL hasParseError; + // the last-accessed folder (for open and save dialogs) + NSString *lastFolder; + // the open (active) document + TikzDocument *document; +} + +/** + * Create and show the main window. + */ +- (id) init; + +/** + * Open a file, asking the user which file to open + */ +- (void) openFile; +/** + * Save the active document to the path it was opened from + * or last saved to, or ask the user where to save it. + */ +- (void) saveActiveDocument; +/** + * Save the active document, asking the user where to save it. + */ +- (void) saveActiveDocumentAs; +/** + * Save the active document as a shape, asking the user what to name it. + */ +- (void) saveActiveDocumentAsShape; +/** + * Quit the application, confirming with the user if there are + * changes to an open document. + */ +- (void) quit; +/** + * If there are changes to an open document, ask the user if they + * want to quit the application, discarding those changes. + * + * @result YES if there are no unsaved changes or the user is happy + * to discard any unsaved changes, NO if the application + * should not quit. + */ +- (BOOL) askCanQuit; + +/** + * Cut the current selection to the clipboard. + */ +- (void) cut; +/** + * Copy the current selection to the clipboard. + */ +- (void) copy; +/** + * Paste from the clipboard to the appropriate place. + */ +- (void) paste; + +/** + * Show the dialog for editing preambles. + */ +- (void) editPreambles; +/** + * Show or update the preview window. + */ +- (void) showPreview; + +/** + * The graph input handler + */ +- (GraphInputHandler*) graphInputHandler; +/** + * The GTK+ window that this class manages. + */ +- (GtkWindow*) gtkWindow; +/** + * The main application configuration file + */ +- (Configuration*) mainConfiguration; +/** + * The menu for the window. + */ +- (Menu*) menu; + +/** + * The document the user is currently editing + */ +- (TikzDocument*) activeDocument; + +/** + * Loads a new, empty document as the active document + */ +- (void) loadEmptyDocument; +/** + * Loads an existing document from a file as the active document + * + * @param path the path to the tikz file containing the document + */ +- (void) loadDocumentFromFile:(NSString*)path; + +/** + * Present an error to the user + * + * (currently just outputs it on the command line) + * + * @param error the error to present + */ +- (void) presentError:(NSError*)error; +/** + * Present an error to the user + * + * (currently just outputs it on the command line) + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentError:(NSError*)error withMessage:(NSString*)message; +/** + * Present an error to the user + * + * (currently just outputs it on the command line) + * + * @param error the error to present + */ +- (void) presentGError:(GError*)error; +/** + * Present an error to the user + * + * (currently just outputs it on the command line) + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentGError:(GError*)error withMessage:(NSString*)message; + +/** + * Save the application configuration to disk + * + * Should be called just before the application exits + */ +- (void) saveConfiguration; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/MainWindow.m b/tikzit/src/linux/MainWindow.m new file mode 100644 index 0000000..ecaf1fa --- /dev/null +++ b/tikzit/src/linux/MainWindow.m @@ -0,0 +1,997 @@ +/* + * 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 "MainWindow.h" + +#import <gtk/gtk.h> +#import "gtkhelpers.h" +#import "clipboard.h" + +#import "Configuration.h" +#import "FileChooserDialog.h" +#import "GraphInputHandler.h" +#import "GraphRenderer.h" +#import "Menu.h" +#import "PreambleEditor.h" +#import "Preambles.h" +#import "Preambles+Storage.h" +#ifdef HAVE_POPPLER +#import "PreviewWindow.h" +#endif +#import "PropertyPane.h" +#import "RecentManager.h" +#import "StyleManager.h" +#import "Shape.h" +#import "StyleManager+Storage.h" +#import "NodeStylesPalette.h" +#import "SupportDir.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + + +// {{{ Internal interfaces +// {{{ Clipboard support + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data); +static void clipboard_release_data (GtkClipboard *clipboard, gpointer clipboard_graph_data); +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action); +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document); + +// }}} +// {{{ Signals + +static void toolbox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); +static void stylebox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window); +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window); +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window); +static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window); +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window); +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action); + +// }}} + +@interface MainWindow (Notifications) +- (void) toolboxWidthChanged:(int)newWidth; +- (void) styleboxWidthChanged:(int)newWidth; +- (void) tikzBufferChanged; +- (void) windowSizeChangedWidth:(int)width height:(int)height; +- (void) documentTikzChanged:(NSNotification*)notification; +- (void) documentSelectionChanged:(NSNotification*)notification; +- (void) undoStackChanged:(NSNotification*)notification; +@end + +@interface MainWindow (InitHelpers) +- (void) _loadConfig; +- (void) _loadStyles; +- (void) _loadPreambles; +- (void) _loadUi; +- (void) _restoreUiState; +- (void) _connectSignals; +@end + +@interface MainWindow (Private) +- (BOOL) _confirmCloseDocumentTo:(NSString*)action; +- (void) _forceLoadDocumentFromFile:(NSString*)path; +- (void) _placeGraphOnClipboard:(Graph*)graph; +- (void) _setHasParseError:(BOOL)hasError; +/** Update the window title. */ +- (void) _updateTitle; +/** Update the window status bar default text. */ +- (void) _updateStatus; +/** Update the displayed tikz code to match the active document. */ +- (void) _updateTikz; +/** Update the undo and redo actions to match the active document's + * undo stack. */ +- (void) _updateUndoActions; +/** Set the last-accessed folder */ +- (void) _setLastFolder:(NSString*)path; +/** Set the active document */ +- (void) _setActiveDocument:(TikzDocument*)newDoc; +@end + +// }}} +// {{{ API + +@implementation MainWindow + +- (id) init { + self = [super init]; + + if (self) { + document = nil; + preambleWindow = nil; + previewWindow = nil; + suppressTikzUpdates = NO; + hasParseError = NO; + + [self _loadConfig]; + [self _loadStyles]; + [self _loadPreambles]; + lastFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + [self _loadUi]; + [self _restoreUiState]; + [self _connectSignals]; + + [self loadEmptyDocument]; + + gtk_widget_show (GTK_WIDGET (mainWindow)); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [configFile release]; + [styleManager release]; + [preambles release]; + [menu release]; + [renderer release]; + [inputHandler release]; + [stylesPalette release]; + [propertyPane release]; + [preambleWindow release]; + [previewWindow release]; + [surface release]; + [lastFolder release]; + [document release]; + + g_object_unref (mainWindow); + g_object_unref (tikzBuffer); + g_object_unref (statusBar); + g_object_unref (propsPane); + g_object_unref (stylesPane); + g_object_unref (graphPane); + g_object_unref (tikzDisp); + + [super dealloc]; +} + +- (void) openFile { + if (![self _confirmCloseDocumentTo:@"open a new document"]) { + return; + } + FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:mainWindow]; + [dialog addStandardFilters]; + if (lastFolder) { + [dialog setCurrentFolder:lastFolder]; + } + + if ([dialog showDialog]) { + [self _forceLoadDocumentFromFile:[dialog filePath]]; + [self _setLastFolder:[dialog currentFolder]]; + } + [dialog destroy]; +} + +- (void) saveActiveDocument { + if ([document path] == nil) { + [self saveActiveDocumentAs]; + } else { + NSError *error = nil; + if (![document save:&error]) { + [self presentError:error]; + } else { + [self _updateTitle]; + } + } +} + +- (void) saveActiveDocumentAs { + FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:mainWindow]; + [dialog addStandardFilters]; + if ([document path] != nil) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if (lastFolder != nil) { + [dialog setCurrentFolder:lastFolder]; + } + [dialog setSuggestedName:[document suggestedFileName]]; + + if ([dialog showDialog]) { + NSString *nfile = [dialog filePath]; + + NSError *error = nil; + if (![document saveToPath:nfile error:&error]) { + [self presentError:error]; + } else { + [self _updateTitle]; + [[RecentManager defaultManager] addRecentFile:nfile]; + [self _setLastFolder:[dialog currentFolder]]; + } + } + [dialog destroy]; +} + +- (void) saveActiveDocumentAsShape { + GtkWidget *dialog = gtk_dialog_new_with_buttons ( + "Save as shape", + mainWindow, + GTK_DIALOG_MODAL, + GTK_STOCK_OK, + GTK_RESPONSE_ACCEPT, + GTK_STOCK_CANCEL, + GTK_RESPONSE_REJECT, + NULL); + GtkBox *content = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))); + GtkWidget *label1 = gtk_label_new ("Please choose a name for the shape"); + GtkWidget *label2 = gtk_label_new ("Name:"); + GtkWidget *input = gtk_entry_new (); + GtkBox *hbox = GTK_BOX (gtk_hbox_new (FALSE, 5)); + gtk_box_pack_start (hbox, label2, FALSE, TRUE, 0); + gtk_box_pack_start (hbox, input, TRUE, TRUE, 0); + gtk_box_pack_start (content, label1, TRUE, TRUE, 5); + gtk_box_pack_start (content, GTK_WIDGET (hbox), TRUE, TRUE, 5); + gtk_widget_show_all (GTK_WIDGET (content)); + gint response = gtk_dialog_run (GTK_DIALOG (dialog)); + while (response == GTK_RESPONSE_ACCEPT) { + response = GTK_RESPONSE_NONE; + NSDictionary *shapeDict = [Shape shapeDictionary]; + const gchar *dialogInput = gtk_entry_get_text (GTK_ENTRY (input)); + NSString *shapeName = [NSString stringWithUTF8String:dialogInput]; + BOOL doSave = NO; + if ([shapeName isEqual:@""]) { + GtkWidget *emptyStrDialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "You must specify a shape name"); + gtk_dialog_run (GTK_DIALOG (emptyStrDialog)); + gtk_widget_destroy (emptyStrDialog); + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } else if ([shapeDict objectForKey:shapeName] != nil) { + GtkWidget *overwriteDialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Do you want to replace the existing shape named '%s'?", + dialogInput); + gint overwriteResp = gtk_dialog_run (GTK_DIALOG (overwriteDialog)); + gtk_widget_destroy (overwriteDialog); + + if (overwriteResp == GTK_RESPONSE_YES) { + doSave = YES; + } else { + response = gtk_dialog_run (GTK_DIALOG (dialog)); + } + } else { + doSave = YES; + } + if (doSave) { + NSError *error = nil; + NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; + NSString *file = [NSString stringWithFormat:@"%@/%@.tikz", userShapeDir, shapeName]; + if (![[NSFileManager defaultManager] ensureDirectoryExists:userShapeDir error:&error]) { + [self presentError:error withMessage:@"Could not create user shape directory"]; + } else { + NSLog (@"Saving shape to %@", file); + if (![document saveCopyToPath:file error:&error]) { + [self presentError:error withMessage:@"Could not save shape file"]; + } else { + [Shape refreshShapeDictionary]; + } + } + } + } + gtk_widget_destroy (dialog); +} + +- (void) quit { + if ([self askCanQuit]) { + gtk_main_quit(); + } +} + +- (BOOL) askCanQuit { + if ([document hasUnsavedChanges]) { + GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + "Are you sure you want to quit without saving the current file?"); + gint result = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + return (result == GTK_RESPONSE_YES) ? YES : NO; + } + + return YES; +} + +- (void) cut { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document cutSelection]]; + } +} + +- (void) copy { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document copySelection]]; + } +} + +- (void) paste { + gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + tikzit_picture_atom, + clipboard_paste_contents, + document); +} + +- (void) editPreambles { + if (preambleWindow == nil) { + preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles]; + [preambleWindow setParentWindow:mainWindow]; + } + [preambleWindow show]; +} + +- (void) showPreview { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:preambles]; + [previewWindow setParentWindow:mainWindow]; + [previewWindow setDocument:document]; + } + [previewWindow show]; +#endif +} + +- (GraphInputHandler*) graphInputHandler { + return inputHandler; +} + +- (GtkWindow*) gtkWindow { + return mainWindow; +} + +- (Configuration*) mainConfiguration { + return configFile; +} + +- (Menu*) menu { + return menu; +} + +- (TikzDocument*) activeDocument { + return document; +} + +- (void) loadEmptyDocument { + if (![self _confirmCloseDocumentTo:@"start a new document"]) { + return; + } + [self _setActiveDocument:[TikzDocument documentWithStyleManager:styleManager]]; +} + +- (void) loadDocumentFromFile:(NSString*)path { + if (![self _confirmCloseDocumentTo:@"open a new document"]) { + return; + } + [self _forceLoadDocumentFromFile:path]; +} + +- (void) presentError:(NSError*)error { + GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + [[error localizedDescription] UTF8String]); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentError:(NSError*)error withMessage:(NSString*)message { + GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + [[error localizedDescription] UTF8String]); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error { + GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error withMessage:(NSString*)message { + GtkWidget *dialog = gtk_message_dialog_new (mainWindow, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) saveConfiguration { + NSError *error = nil; + + if (preambles != nil) { + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([preamblesDir UTF8String], 700); + [preambles storeToDirectory:preamblesDir]; + [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; + } + + [styleManager saveStylesUsingConfigurationName:@"styles"]; + [propertyPane saveUiStateToConfig:configFile group:@"PropertyPane"]; + + if (lastFolder != nil) { + [configFile setStringEntry:@"lastFolder" inGroup:@"Paths" value:lastFolder]; + } + + if (![configFile writeToStoreWithError:&error]) { + logError (error, @"Could not write config file"); + } +} + +- (void) zoomIn { + [surface zoomIn]; +} + +- (void) zoomOut { + [surface zoomOut]; +} + +- (void) zoomReset { + [surface zoomReset]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation MainWindow (Notifications) +- (void) toolboxWidthChanged:(int)newWidth { + [configFile setIntegerEntry:@"toolboxWidth" inGroup:@"mainWindow" value:newWidth]; +} + +- (void) styleboxWidthChanged:(int)newWidth { + [configFile setIntegerEntry:@"styleboxWidth" inGroup:@"mainWindow" value:newWidth]; +} + +- (void) graphHeightChanged:(int)newHeight { + [configFile setIntegerEntry:@"graphHeight" inGroup:@"mainWindow" value:newHeight]; +} + +- (void) tikzBufferChanged { + if (!suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + GtkTextIter start, end; + gtk_text_buffer_get_bounds (tikzBuffer, &start, &end); + gchar *text = gtk_text_buffer_get_text (tikzBuffer, &start, &end, FALSE); + + BOOL success = [document setTikz:[NSString stringWithUTF8String:text]]; + [self _setHasParseError:!success]; + + g_free (text); + + suppressTikzUpdates = FALSE; + } +} + +- (void) windowSizeChangedWidth:(int)width height:(int)height { + if (width > 0 && height > 0) { + NSNumber *w = [NSNumber numberWithInt:width]; + NSNumber *h = [NSNumber numberWithInt:height]; + NSMutableArray *size = [NSMutableArray arrayWithCapacity:2]; + [size addObject:w]; + [size addObject:h]; + [configFile setIntegerListEntry:@"windowSize" inGroup:@"mainWindow" value:size]; + } +} + +- (void) documentTikzChanged:(NSNotification*)notification { + [self _updateTitle]; + [self _updateTikz]; +} + +- (void) documentSelectionChanged:(NSNotification*)notification { + [self _updateStatus]; + [menu notifySelectionChanged:[document pickSupport]]; +} + +- (void) undoStackChanged:(NSNotification*)notification { + [self _updateUndoActions]; +} +@end + +// }}} +// {{{ InitHelpers + +@implementation MainWindow (InitHelpers) + +- (void) _loadConfig { + NSError *error = nil; + configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error]; + if (error != nil) { + logError (error, @"WARNING: Failed to load configuration"); + } +} + +- (void) _loadStyles { + styleManager = [[StyleManager alloc] init]; + [styleManager loadStylesUsingConfigurationName:@"styles"]; +} + +// must happen after _loadStyles +- (void) _loadPreambles { + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + preambles = [[Preambles alloc] initFromDirectory:preamblesDir]; + [preambles setStyleManager:styleManager]; + NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"]; + if (selectedPreamble != nil) { + [preambles setSelectedPreambleName:selectedPreamble]; + } +} + +- (void) _loadUi { + mainWindow = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + g_object_ref_sink (mainWindow); + gtk_window_set_title (mainWindow, "TikZiT"); + gtk_window_set_default_size (mainWindow, 700, 400); + + GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0)); + gtk_widget_show (GTK_WIDGET (mainLayout)); + gtk_container_add (GTK_CONTAINER (mainWindow), GTK_WIDGET (mainLayout)); + + menu = [[Menu alloc] initForMainWindow:self]; + + GtkWidget *menubar = [menu menubar]; + gtk_box_pack_start (mainLayout, menubar, FALSE, TRUE, 0); + gtk_box_reorder_child (mainLayout, menubar, 0); + gtk_widget_show (menubar); + + GtkWidget *toolbarBox = gtk_handle_box_new (); + gtk_box_pack_start (mainLayout, toolbarBox, FALSE, TRUE, 0); + gtk_widget_show (toolbarBox); + gtk_container_add (GTK_CONTAINER (toolbarBox), [menu toolbar]); + gtk_widget_show ([menu toolbar]); + + propsPane = GTK_PANED (gtk_hpaned_new ()); + g_object_ref_sink (propsPane); + gtk_widget_show (GTK_WIDGET (propsPane)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (propsPane), TRUE, TRUE, 0); + + propertyPane = [[PropertyPane alloc] init]; + gtk_paned_pack1 (propsPane, [propertyPane widget], FALSE, TRUE); + gtk_widget_show ([propertyPane widget]); + + stylesPane = GTK_PANED (gtk_hpaned_new ()); + g_object_ref_sink (stylesPane); + gtk_widget_show (GTK_WIDGET (stylesPane)); + gtk_paned_pack2 (propsPane, GTK_WIDGET (stylesPane), TRUE, TRUE); + + stylesPalette = [[NodeStylesPalette alloc] initWithManager:styleManager]; + gtk_paned_pack2 (stylesPane, [stylesPalette widget], FALSE, TRUE); + gtk_widget_show ([stylesPalette widget]); + + graphPane = GTK_PANED (gtk_vpaned_new ()); + g_object_ref_sink (graphPane); + gtk_widget_show (GTK_WIDGET (graphPane)); + gtk_paned_pack1 (stylesPane, GTK_WIDGET (graphPane), TRUE, TRUE); + + surface = [[WidgetSurface alloc] init]; + gtk_widget_show ([surface widget]); + gtk_paned_pack1 (graphPane, [surface widget], TRUE, TRUE); + [surface setDefaultScale:50.0f]; + [surface setKeepCentered:YES]; + [surface setGrabsFocusOnClick:YES]; + renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; + + inputHandler = [[GraphInputHandler alloc] initWithGraphRenderer:renderer]; + [surface setInputDelegate:inputHandler]; + + tikzBuffer = gtk_text_buffer_new (NULL); + g_object_ref_sink (tikzBuffer); + GtkWidget *tikzScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (tikzScroller); + + tikzDisp = gtk_text_view_new_with_buffer (tikzBuffer); + g_object_ref_sink (tikzDisp); + gtk_widget_show (tikzDisp); + gtk_container_add (GTK_CONTAINER (tikzScroller), tikzDisp); + gtk_paned_pack2 (graphPane, tikzScroller, FALSE, TRUE); + + statusBar = GTK_STATUSBAR (gtk_statusbar_new ()); + gtk_widget_show (GTK_WIDGET (statusBar)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (statusBar), FALSE, TRUE, 0); + + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + update_paste_action (clipboard, NULL, [menu pasteAction]); +} + +- (void) _restoreUiState { + NSArray *windowSize = [configFile integerListEntry:@"windowSize" inGroup:@"mainWindow"]; + if (windowSize && [windowSize count] == 2) { + gint width = [[windowSize objectAtIndex:0] intValue]; + gint height = [[windowSize objectAtIndex:1] intValue]; + if (width > 0 && height > 0) { + gtk_window_set_default_size (mainWindow, width, height); + } + } + int panePos = [configFile integerEntry:@"toolboxWidth" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (propsPane, panePos); + } + panePos = [configFile integerEntry:@"styleboxWidth" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (stylesPane, panePos); + } + panePos = [configFile integerEntry:@"graphHeight" inGroup:@"mainWindow"]; + if (panePos > 0) { + gtk_paned_set_position (graphPane, panePos); + } + [propertyPane restoreUiStateFromConfig:configFile group:@"PropertyPane"]; +} + +- (void) _connectSignals { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + g_signal_connect (G_OBJECT (clipboard), + "owner-change", + G_CALLBACK (update_paste_action), + [menu pasteAction]); + g_signal_connect (G_OBJECT (mainWindow), + "key-press-event", + G_CALLBACK (tz_hijack_key_press), + NULL); + g_signal_connect (G_OBJECT (propsPane), + "notify::position", + G_CALLBACK (toolbox_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (stylesPane), + "notify::position", + G_CALLBACK (stylebox_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (graphPane), + "notify::position", + G_CALLBACK (graph_divider_position_changed_cb), + self); + g_signal_connect (G_OBJECT (tikzBuffer), + "changed", + G_CALLBACK (tikz_buffer_changed_cb), + self); + g_signal_connect (G_OBJECT (mainWindow), + "delete-event", + G_CALLBACK (main_window_delete_event_cb), + self); + g_signal_connect (G_OBJECT (mainWindow), + "destroy", + G_CALLBACK (main_window_destroy_cb), + self); + g_signal_connect (G_OBJECT (mainWindow), + "configure-event", + G_CALLBACK (main_window_configure_event_cb), + self); +} +@end + +// }}} +// {{{ Private + +@implementation MainWindow (Private) + +- (BOOL) _confirmCloseDocumentTo:(NSString*)action { + BOOL proceed = YES; + if ([document hasUnsavedChanges]) { + NSString *message = [NSString stringWithFormat:@"You have unsaved changes to the current document, which will be lost if you %@. Are you sure you want to continue?", action]; + GtkWidget *dialog = gtk_message_dialog_new (NULL, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_YES_NO, + [message UTF8String]); + gtk_window_set_title(GTK_WINDOW(dialog), "Close current document?"); + proceed = gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES; + gtk_widget_destroy (dialog); + } + return proceed; +} + +- (void) _forceLoadDocumentFromFile:(NSString*)path { + NSError *error = nil; + TikzDocument *d = [TikzDocument documentFromFile:path styleManager:styleManager error:&error]; + if (d != nil) { + [self _setActiveDocument:d]; + [[RecentManager defaultManager] addRecentFile:path]; + } else { + [self presentError:error withMessage:@"Could not open file"]; + [[RecentManager defaultManager] removeRecentFile:path]; + } +} + +- (void) _placeGraphOnClipboard:(Graph*)graph { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + + static const GtkTargetEntry targets[] = { + { "TIKZITPICTURE", 0, TARGET_TIKZIT_PICTURE }, + { "UTF8_STRING", 0, TARGET_UTF8_STRING } }; + + gtk_clipboard_set_with_data (clipboard, + targets, G_N_ELEMENTS (targets), + clipboard_provide_data, + clipboard_release_data, + clipboard_graph_data_new (graph)); +} + +- (void) _setHasParseError:(BOOL)hasError { + if (hasError && !hasParseError) { + gtk_statusbar_push (statusBar, 1, "Parse error"); + GdkColor color = {0, 65535, 61184, 61184}; + gtk_widget_modify_base (tikzDisp, GTK_STATE_NORMAL, &color); + } else if (!hasError && hasParseError) { + gtk_statusbar_pop (statusBar, 1); + gtk_widget_modify_base (tikzDisp, GTK_STATE_NORMAL, NULL); + } + hasParseError = hasError; +} + +- (void) _updateUndoActions { + [menu setUndoActionEnabled:[document canUndo]]; + [menu setUndoActionDetail:[document undoName]]; + + [menu setRedoActionEnabled:[document canRedo]]; + [menu setRedoActionDetail:[document redoName]]; +} + +- (void) _updateTitle { + NSString *title = [NSString stringWithFormat:@"TikZiT - %@%s", + [document name], + ([document hasUnsavedChanges] ? "*" : "")]; + gtk_window_set_title(mainWindow, [title UTF8String]); +} + +- (void) _updateStatus { + GString *buffer = g_string_sized_new (30); + gchar *nextNode = 0; + + for (Node *n in [[document pickSupport] selectedNodes]) { + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Nodes %s", nextNode); + } else { + g_string_append_printf(buffer, ", %s", nextNode); + } + } + nextNode = (gchar *)[[n name] UTF8String]; + } + if (nextNode) { + if (buffer->len == 0) { + g_string_printf(buffer, "Node %s is selected", nextNode); + } else { + g_string_append_printf(buffer, " and %s are selected", nextNode); + } + } + + if (buffer->len == 0) { + int nrNodes = [[[document graph] nodes] count]; + int nrEdges = [[[document graph] edges] count]; + g_string_printf(buffer, "Graph has %d node%s and %d edge%s", + nrNodes, + nrNodes!=1 ? "s" : "", + nrEdges, + nrEdges!=1 ? "s" : ""); + } + gtk_statusbar_pop(statusBar, 0); + gtk_statusbar_push(statusBar, 0, buffer->str); + + g_string_free (buffer, TRUE); +} + +- (void) _updateTikz { + if (document != nil && !suppressTikzUpdates) { + suppressTikzUpdates = TRUE; + + if (document != nil) { + const char *tikzString = [[document tikz] UTF8String]; + gtk_text_buffer_set_text (tikzBuffer, tikzString, -1); + } else { + gtk_text_buffer_set_text (tikzBuffer, "", -1); + } + [self _setHasParseError:NO]; + + suppressTikzUpdates = FALSE; + } +} + +- (void) _setLastFolder:(NSString*)path { + [path retain]; + [lastFolder release]; + lastFolder = path; +} + +- (void) _setActiveDocument:(TikzDocument*)newDoc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + + [newDoc retain]; + [document release]; + document = newDoc; + + [renderer setDocument:document]; + [stylesPalette setDocument:document]; + [propertyPane setDocument:document]; +#ifdef HAVE_POPPLER + [previewWindow setDocument:document]; +#endif + [self _updateTikz]; + [self _updateTitle]; + [self _updateStatus]; + [self _updateUndoActions]; + [menu notifySelectionChanged:[document pickSupport]]; + [inputHandler resetState]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentTikzChanged:) + name:@"TikzChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(undoStackChanged:) + name:@"UndoStackChanged" object:document]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"NodeSelectionChanged" object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(documentSelectionChanged:) + name:@"EdgeSelectionChanged" object:[document pickSupport]]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void toolbox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gint position; + g_object_get (gobject, "position", &position, NULL); + [window toolboxWidthChanged:position]; + [pool drain]; +} + +static void stylebox_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gint position; + g_object_get (gobject, "position", &position, NULL); + [window styleboxWidthChanged:position]; + [pool drain]; +} + +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gint position; + g_object_get (gobject, "position", &position, NULL); + [window graphHeightChanged:position]; + [pool drain]; +} + +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window tikzBufferChanged]; + [pool drain]; +} + +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + return ![window askCanQuit]; + [pool drain]; +} + +static void main_window_destroy_cb (GtkWidget *widget, MainWindow *window) { + gtk_main_quit(); +} + +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window windowSizeChangedWidth:event->width height:event->height]; + [pool drain]; + return FALSE; +} + +static void clipboard_provide_data (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer clipboard_graph_data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + ClipboardGraphData *data = (ClipboardGraphData*)clipboard_graph_data; + if (info == TARGET_UTF8_STRING || info == TARGET_TIKZIT_PICTURE) { + clipboard_graph_data_convert (data); + GdkAtom target = (info == TARGET_UTF8_STRING) ? utf8_atom : tikzit_picture_atom; + gtk_selection_data_set (selection_data, + target, + 8*sizeof(gchar), + (guchar*)data->tikz, + data->tikz_length); + } + + [pool drain]; +} + +static void clipboard_release_data (GtkClipboard *clipboard, gpointer data) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + clipboard_graph_data_free ((ClipboardGraphData*)data); + [pool drain]; +} + +static void clipboard_check_targets (GtkClipboard *clipboard, + GdkAtom *atoms, + gint n_atoms, + gpointer action) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gboolean found = FALSE; + for (gint i = 0; i < n_atoms; ++i) { + if (atoms[i] == tikzit_picture_atom) { + found = TRUE; + break; + } + } + gtk_action_set_sensitive (GTK_ACTION (action), found); + + [pool drain]; +} + +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action) { + gtk_action_set_sensitive (action, FALSE); + gtk_clipboard_request_targets (clipboard, clipboard_check_targets, action); +} + +static void clipboard_paste_contents (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer document) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *doc = (TikzDocument*)document; + gint length = gtk_selection_data_get_length (selection_data); + if (length >= 0) { + const guchar *raw_data = gtk_selection_data_get_data (selection_data); + gchar *data = g_new (gchar, length+1); + g_strlcpy (data, (const gchar *)raw_data, length+1); + NSString *tikz = [NSString stringWithUTF8String:data]; + if (tikz != nil) { + [doc pasteFromTikz:tikz]; + } + g_free (data); + } + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/Menu.h b/tikzit/src/linux/Menu.h new file mode 100644 index 0000000..024c9e0 --- /dev/null +++ b/tikzit/src/linux/Menu.h @@ -0,0 +1,99 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * Stuff stolen from glade-window.c in Glade: + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2007 Vincent Geddes. + * + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +@class MainWindow; +@class PickSupport; + +/** + * Manages the menu and toolbar for the main window. + */ +@interface Menu: NSObject { + MainWindow *mainWindow; + GtkUIManager *ui; + GtkActionGroup *staticActions; + GtkActionGroup *documentActions; +// GtkActionGroup *documents_list_menu_actions; + GtkAction *undoAction; + GtkAction *redoAction; + GtkAction *pasteAction; + GtkAction **nodeSelBasedActions; + guint nodeSelBasedActionCount; + GtkAction **selBasedActions; + guint selBasedActionCount; +} + +/** + * Constructs the menu and toolbar for @p window + * + * @param window the mainwindow that will be acted upon by the various + * menu items and toolbar buttons. + */ +- (id) initForMainWindow:(MainWindow*)window; + +/** + * The menubar widget, to be inserted into the main window + */ +- (GtkWidget*) menubar; +/** + * The toolbar widget, to be inserted into the main window + */ +- (GtkWidget*) toolbar; +/** + * The main window object passed to initForMainWindow + */ +- (MainWindow*) mainWindow; + +/** + * Enables or disables the undo action + */ +- (void) setUndoActionEnabled:(BOOL)enabled; +/** + * Sets the text that describes what action will be undone + * + * @param detail a text description of the action, or nil + */ +- (void) setUndoActionDetail:(NSString*)detail; +/** + * Enables or disables the redo action + */ +- (void) setRedoActionEnabled:(BOOL)enabled; +/** + * Sets the text that describes what action will be redone + * + * @param detail a text description of the action, or nil + */ +- (void) setRedoActionDetail:(NSString*)detail; + +/** + * Gets the paste action + */ +- (GtkAction*) pasteAction; + +/** + * Enables or disables the actions that act on a selection + */ +- (void) notifySelectionChanged:(PickSupport*)pickSupport; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Menu.m b/tikzit/src/linux/Menu.m new file mode 100644 index 0000000..c7eb16d --- /dev/null +++ b/tikzit/src/linux/Menu.m @@ -0,0 +1,779 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * Stuff stolen from glade-window.c in Glade: + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2007 Vincent Geddes. + * + * 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 "Menu.h" + +#import "MainWindow.h" +#import "GraphInputHandler.h" +#import "Configuration.h" +#import "PickSupport.h" +#import "Shape.h" +#import "TikzDocument.h" + +#import <glib.h> +#ifdef _ +#undef _ +#endif +#import <glib/gi18n.h> +#import <gtk/gtk.h> + +#import "gtkhelpers.h" + +#define ACTION_GROUP_STATIC "TZStatic" +#define ACTION_GROUP_DOCUMENT "TZDocument" +#define ACTION_GROUP_DOCUMENTS_LIST_MENU "TZDocumentsList" + +#import "logo.h" +#include <gdk-pixbuf/gdk-pixdata.h> +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "icondata.m" +#pragma GCC diagnostic pop + + +// {{{ Callbacks + +static void new_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window loadEmptyDocument]; + [pool drain]; +} + +static void open_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window openFile]; + [pool drain]; +} + +static void save_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocument]; + [pool drain]; +} + +static void save_as_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAs]; + [pool drain]; +} + +static void save_as_shape_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAsShape]; + [pool drain]; +} + +static void refresh_shapes_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [Shape refreshShapeDictionary]; + [pool drain]; +} + +static void quit_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window quit]; + [pool drain]; +} + +static void help_cb (GtkAction *action, MainWindow *window) { + GError *gerror = NULL; + gtk_show_uri (NULL, "http://tikzit.sourceforge.net/manual.html", GDK_CURRENT_TIME, &gerror); + if (gerror != NULL) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + logGError (gerror, @"Could not show help"); + [pool drain]; + } +} + +static void about_cb (GtkAction *action, MainWindow *window) { + static const gchar * const authors[] = + { "Aleks Kissinger <aleks0@gmail.com>", + "Chris Heunen <chrisheunen@gmail.com>", + "Alex Merry <dev@randomguy3.me.uk>", + NULL }; + + static const gchar license[] = + N_("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 2 of the " + "License, or (at your option) any later version." + "\n\n" + "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." + "\n\n" + "You should have received a copy of the GNU General Public License " + "along with TikZiT; if not, write to the Free Software " + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, " + "MA 02110-1301, USA."); + + static const gchar copyright[] = + "Copyright \xc2\xa9 2010-2011 Aleks Kissinger, Chris Heunen and Alex Merry."; + + GdkPixbuf *logo = get_logo (LOGO_SIZE_128); + gtk_show_about_dialog (GTK_WINDOW ([window gtkWindow]), + "program-name", PACKAGE_NAME, + "logo", logo, + "authors", authors, + "translator-credits", _("translator-credits"), + "comments", _("A graph manipulation program for pgf/tikz graphs"), + "license", _(license), + "wrap-license", TRUE, + "copyright", copyright, + "version", PACKAGE_VERSION, + "website", "http://tikzit.sourceforge.net", + NULL); + g_object_unref (logo); +} + +static void undo_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window activeDocument]; + if ([document canUndo]) { + [document undo]; + } else { + g_warning ("Can't undo!\n"); + gtk_action_set_sensitive (action, FALSE); + } + + [pool drain]; +} + +static void redo_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window activeDocument]; + if ([document canRedo]) { + [document redo]; + } else { + g_warning ("Can't redo!\n"); + gtk_action_set_sensitive (action, FALSE); + } + + [pool drain]; +} + +static void cut_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window cut]; + [pool drain]; +} + +static void copy_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window copy]; + [pool drain]; +} + +static void paste_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window paste]; + [pool drain]; +} + +static void delete_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window activeDocument] removeSelected]; + [pool drain]; +} + +static void select_all_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window activeDocument]; + [[document pickSupport] selectAllNodes:[[document graph] nodes]]; + [pool drain]; +} + +static void deselect_all_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window activeDocument]; + [[document pickSupport] deselectAllNodes]; + [[document pickSupport] deselectAllEdges]; + [pool drain]; +} + +static void flip_horiz_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window activeDocument] flipSelectedNodesHorizontally]; + [pool drain]; +} + +static void flip_vert_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window activeDocument] flipSelectedNodesVertically]; + [pool drain]; +} + +static void show_preamble_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window editPreambles]; + [pool drain]; +} + +static void zoom_in_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomIn]; + [pool drain]; +} + +static void zoom_out_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomOut]; + [pool drain]; +} + +static void zoom_reset_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomReset]; + [pool drain]; +} + +#ifdef HAVE_POPPLER +static void show_preview_cb (GtkAction *action, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window showPreview]; + [pool drain]; +} +#endif + +static void input_mode_change_cb (GtkRadioAction *action, GtkRadioAction *current, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window graphInputHandler] setMode:(InputMode)gtk_radio_action_get_current_value (action)]; + [pool drain]; +} + +static void toolbar_style_change_cb (GtkRadioAction *action, GtkRadioAction *current, Menu *menu) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gint value = gtk_radio_action_get_current_value (action); + gtk_toolbar_set_style (GTK_TOOLBAR ([menu toolbar]), (GtkToolbarStyle)value); + [[[menu mainWindow] mainConfiguration] setIntegerEntry:@"toolbarStyle" inGroup:@"UI" value:value]; + + [pool drain]; +} + +static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, MainWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gchar *uri, *path; + GError *error = NULL; + + uri = gtk_recent_chooser_get_current_uri (chooser); + + path = g_filename_from_uri (uri, NULL, NULL); + if (error) { + g_warning ("Could not convert uri \"%s\" to a local path: %s", uri, error->message); + g_error_free (error); + return; + } + + NSString *nspath = [NSString stringWithGlibFilename:path]; + if (error) { + g_warning ("Could not convert filename \"%s\" to an NSString: %s", path, error->message); + g_error_free (error); + return; + } + [window loadDocumentFromFile:nspath]; + + g_free (uri); + g_free (path); + + [pool drain]; +} + + + +// }}} +// {{{ UI XML + +static const gchar ui_info[] = +"<ui>" +" <menubar name='MenuBar'>" +" <menu action='FileMenu'>" +" <menuitem action='New'/>" +" <menuitem action='Open'/>" +" <menuitem action='OpenRecent'/>" +" <separator/>" +" <menuitem action='Save'/>" +" <menuitem action='SaveAs'/>" +" <separator/>" +" <menuitem action='SaveAsShape'/>" +" <menuitem action='RefreshShapes'/>" +" <separator/>" +//" <menuitem action='Close'/>" +" <menuitem action='Quit'/>" +" </menu>" +" <menu action='EditMenu'>" +" <menu action='Tool'>" +" <menuitem action='SelectMode'/>" +" <menuitem action='CreateNodeMode'/>" +" <menuitem action='DrawEdgeMode'/>" +" <menuitem action='BoundingBoxMode'/>" +" <menuitem action='HandMode'/>" +" </menu>" +" <separator/>" +" <menuitem action='Undo'/>" +" <menuitem action='Redo'/>" +" <separator/>" +" <menuitem action='Cut'/>" +" <menuitem action='Copy'/>" +" <menuitem action='Paste'/>" +" <menuitem action='Delete'/>" +" <separator/>" +" <menuitem action='SelectAll'/>" +" <menuitem action='DeselectAll'/>" +" <separator/>" +" <menuitem action='FlipVert'/>" +" <menuitem action='FlipHoriz'/>" +" </menu>" +" <menu action='ViewMenu'>" +" <menu action='ToolbarStyle'>" +" <menuitem action='ToolbarIconsOnly'/>" +" <menuitem action='ToolbarTextOnly'/>" +" <menuitem action='ToolbarTextIcons'/>" +" <menuitem action='ToolbarTextIconsHoriz'/>" +" </menu>" +/* +" <menuitem action='ToolbarVisible'/>" +" <menuitem action='StatusbarVisible'/>" +*/ +" <menuitem action='ShowPreamble'/>" +#ifdef HAVE_POPPLER +" <menuitem action='ShowPreview'/>" +#endif +" <menu action='Zoom'>" +" <menuitem action='ZoomIn'/>" +" <menuitem action='ZoomOut'/>" +" <menuitem action='ZoomReset'/>" +" </menu>" +" </menu>" +/* +" <menu action='Window'>" +" <placeholder name='DocumentsListPlaceholder'/>" +" </menu>" +*/ +" <menu action='HelpMenu'>" +" <menuitem action='HelpManual'/>" +" <separator/>" +" <menuitem action='About'/>" +" </menu>" +" </menubar>" +" <toolbar name='ToolBar'>" +" <toolitem action='New'/>" +" <toolitem action='Open'/>" +" <toolitem action='Save'/>" +" <separator/>" +" <toolitem action='Cut'/>" +" <toolitem action='Copy'/>" +" <toolitem action='Paste'/>" +" <separator/>" +" <toolitem action='SelectMode'/>" +" <toolitem action='CreateNodeMode'/>" +" <toolitem action='DrawEdgeMode'/>" +" <toolitem action='BoundingBoxMode'/>" +" <toolitem action='HandMode'/>" +" </toolbar>" +"</ui>"; + + + +// }}} +// {{{ Actions + +static GtkActionEntry static_entries[] = { + /* + Fields: + * action name + * stock id or name of icon for action + * label for action (mark for translation with N_) + * accelerator (as understood by gtk_accelerator_parse()) + * tooltip (mark for translation with N_) + * callback + */ + { "FileMenu", NULL, N_("_File") }, + { "EditMenu", NULL, N_("_Edit") }, + { "ViewMenu", NULL, N_("_View") }, + //{ "ProjectMenu", NULL, N_("_Projects") }, + { "HelpMenu", NULL, N_("_Help") }, + //{ "UndoMenu", NULL, NULL }, + //{ "RedoMenu", NULL, NULL }, + + /* FileMenu */ + { "New", GTK_STOCK_NEW, NULL, "<control>N", + N_("Create a new graph"), G_CALLBACK (new_cb) }, + + { "Open", GTK_STOCK_OPEN, N_("_Open\342\200\246") ,"<control>O", + N_("Open a graph"), G_CALLBACK (open_cb) }, + + { "OpenRecent", NULL, N_("Open _Recent") }, + + { "RefreshShapes", NULL, N_("_Refresh shapes"), NULL, + N_(""), G_CALLBACK (refresh_shapes_cb) }, + + { "Quit", GTK_STOCK_QUIT, NULL, "<control>Q", + N_("Quit the program"), G_CALLBACK (quit_cb) }, + + /* EditMenu */ + { "Tool", NULL, N_("_Tool") }, + + /* ViewMenu */ + { "ToolbarStyle", NULL, N_("_Toolbar style") }, + + { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL, + N_("Edit the preambles used to generate the preview"), G_CALLBACK (show_preamble_cb) }, + + { "Zoom", NULL, N_("_Zoom") }, + + { "ZoomIn", GTK_STOCK_ZOOM_IN, NULL, "<control>plus", + NULL, G_CALLBACK (zoom_in_cb) }, + + { "ZoomOut", GTK_STOCK_ZOOM_OUT, NULL, "<control>minus", + NULL, G_CALLBACK (zoom_out_cb) }, + + { "ZoomReset", GTK_STOCK_ZOOM_100, N_("_Reset zoom"), "<control>0", + NULL, G_CALLBACK (zoom_reset_cb) }, + + /* HelpMenu */ + { "HelpManual", GTK_STOCK_HELP, N_("_Online manual"), "F1", + N_("TikZiT manual (online)"), G_CALLBACK (help_cb) }, + + { "About", GTK_STOCK_ABOUT, NULL, NULL, + N_("About this application"), G_CALLBACK (about_cb) }, +}; + +static guint n_static_entries = G_N_ELEMENTS (static_entries); + +static GtkActionEntry document_entries[] = { + + /* FileMenu */ + { "Save", GTK_STOCK_SAVE, NULL, "<control>S", + N_("Save the current graph"), G_CALLBACK (save_cb) }, + + { "SaveAs", GTK_STOCK_SAVE_AS, N_("Save _As\342\200\246"), NULL, + N_("Save the current graph with a different name"), G_CALLBACK (save_as_cb) }, + + { "SaveAsShape", NULL, N_("Save As S_hape\342\200\246"), NULL, + N_("Save the current graph as a shape for use in styles"), G_CALLBACK (save_as_shape_cb) }, + +/* + { "Close", GTK_STOCK_CLOSE, NULL, "<control>W", + N_("Close the current graph"), G_CALLBACK (close_cb) }, +*/ + + /* EditMenu */ + { "Undo", GTK_STOCK_UNDO, NULL, "<control>Z", + N_("Undo the last action"), G_CALLBACK (undo_cb) }, + + { "Redo", GTK_STOCK_REDO, NULL, "<shift><control>Z", + N_("Redo the last action"), G_CALLBACK (redo_cb) }, + + { "Cut", GTK_STOCK_CUT, NULL, NULL, + N_("Cut the selection"), G_CALLBACK (cut_cb) }, + + { "Copy", GTK_STOCK_COPY, NULL, NULL, + N_("Copy the selection"), G_CALLBACK (copy_cb) }, + + { "Paste", GTK_STOCK_PASTE, NULL, NULL, + N_("Paste the clipboard"), G_CALLBACK (paste_cb) }, + + { "Delete", GTK_STOCK_DELETE, NULL, "Delete", + N_("Delete the selection"), G_CALLBACK (delete_cb) }, + + { "SelectAll", GTK_STOCK_SELECT_ALL, NULL, "<control>A", + N_("Select all nodes on the graph"), G_CALLBACK (select_all_cb) }, + + { "DeselectAll", NULL, N_("D_eselect all"), "<shift><control>A", + N_("Deselect everything"), G_CALLBACK (deselect_all_cb) }, + + { "FlipHoriz", NULL, N_("Flip nodes _horizonally"), NULL, + N_("Flip the selected nodes horizontally"), G_CALLBACK (flip_horiz_cb) }, + + { "FlipVert", NULL, N_("Flip nodes _vertically"), NULL, + N_("Flip the selected nodes vertically"), G_CALLBACK (flip_vert_cb) }, + + /* ViewMenu */ +#ifdef HAVE_POPPLER + { "ShowPreview", NULL, N_("_Preview"), "<control>L", + N_("See the graph as it will look when rendered in LaTeX"), G_CALLBACK (show_preview_cb) }, +#endif +}; +static guint n_document_entries = G_N_ELEMENTS (document_entries); + +static GtkRadioActionEntry mode_entries[] = { + /* + Fields: + * action name + * stock id or name of icon for action + * label for action (mark for translation with N_) + * accelerator (as understood by gtk_accelerator_parse()) + * tooltip (mark for translation with N_) + * value (see gtk_radio_action_get_current_value()) + */ + + { "SelectMode", NULL, N_("_Select"), "<control><shift>s", + N_("Select, move and edit nodes and edges"), (gint)SelectMode }, + + { "CreateNodeMode", NULL, N_("_Create nodes"), "<control><shift>c", + N_("Create new nodes"), (gint)CreateNodeMode }, + + { "DrawEdgeMode", NULL, N_("_Draw edges"), "<control><shift>e", + N_("Draw new edges"), (gint)DrawEdgeMode }, + + { "BoundingBoxMode", NULL, N_("_Bounding box"), "<control><shift>x", + N_("Set the bounding box"), (gint)BoundingBoxMode }, + + { "HandMode", NULL, N_("_Pan"), "<control><shift>f", + N_("Move the diagram to view different parts"), (gint)HandMode }, +}; +static guint n_mode_entries = G_N_ELEMENTS (mode_entries); + +static GtkRadioActionEntry toolbar_style_entries[] = { + /* + Fields: + * action name + * stock id or name of icon for action + * label for action (mark for translation with N_) + * accelerator (as understood by gtk_accelerator_parse()) + * tooltip (mark for translation with N_) + * value (see gtk_radio_action_get_current_value()) + */ + + { "ToolbarIconsOnly", NULL, N_("_Icons only"), NULL, + N_("Show only icons on the toolbar"), (gint)GTK_TOOLBAR_ICONS }, + + { "ToolbarTextOnly", NULL, N_("_Text only"), NULL, + N_("Show only text on the toolbar"), (gint)GTK_TOOLBAR_TEXT }, + + { "ToolbarTextIcons", NULL, N_("Text _below icons"), NULL, + N_("Show icons on the toolbar with text below"), (gint)GTK_TOOLBAR_BOTH }, + + { "ToolbarTextIconsHoriz", NULL, N_("Text be_side icons"), NULL, + N_("Show icons on the toolbar with text beside"), (gint)GTK_TOOLBAR_BOTH_HORIZ }, +}; +static guint n_toolbar_style_entries = G_N_ELEMENTS (toolbar_style_entries); + +// }}} +// {{{ Helper methods + + +static void +set_tool_button_image (GtkToolButton *button, const GdkPixdata *image_data) +{ + GtkWidget *image = NULL; + + if (image_data) { + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (image_data, FALSE, NULL); + image = gtk_image_new_from_pixbuf (buf); + g_object_unref (buf); + } + + gtk_tool_button_set_icon_widget (button, image); + + if (image) { + gtk_widget_show (image); + } +} + +GtkWidget * +create_recent_chooser_menu () +{ + GtkWidget *recent_menu; + GtkRecentFilter *filter; + + recent_menu = gtk_recent_chooser_menu_new_for_manager (gtk_recent_manager_get_default ()); + + gtk_recent_chooser_set_local_only (GTK_RECENT_CHOOSER (recent_menu), TRUE); + gtk_recent_chooser_set_show_icons (GTK_RECENT_CHOOSER (recent_menu), FALSE); + gtk_recent_chooser_set_sort_type (GTK_RECENT_CHOOSER (recent_menu), GTK_RECENT_SORT_MRU); + gtk_recent_chooser_menu_set_show_numbers (GTK_RECENT_CHOOSER_MENU (recent_menu), TRUE); + + filter = gtk_recent_filter_new (); + gtk_recent_filter_add_application (filter, g_get_application_name()); + gtk_recent_chooser_set_filter (GTK_RECENT_CHOOSER (recent_menu), filter); + + return recent_menu; +} + + + +// }}} +// {{{ API + +@implementation Menu + +- (id) initForMainWindow:(MainWindow*)window +{ + self = [super init]; + if (!self) { + return nil; + } + + mainWindow = window; + + GError *error = NULL; + + staticActions = gtk_action_group_new (ACTION_GROUP_STATIC); + //gtk_action_group_set_translation_domain (staticActions, GETTEXT_PACKAGE); + + gtk_action_group_add_actions (staticActions, + static_entries, + n_static_entries, + window); + gtk_action_group_add_radio_actions (staticActions, mode_entries, + n_mode_entries, (gint)SelectMode, + G_CALLBACK (input_mode_change_cb), window); + GtkToolbarStyle style; + g_object_get (G_OBJECT (gtk_settings_get_default ()), "gtk-toolbar-style", &style, NULL); + gtk_action_group_add_radio_actions (staticActions, toolbar_style_entries, + n_toolbar_style_entries, style, + G_CALLBACK (toolbar_style_change_cb), self); + + documentActions = gtk_action_group_new (ACTION_GROUP_DOCUMENT); + //gtk_action_group_set_translation_domain (documentActions, GETTEXT_PACKAGE); + + gtk_action_group_add_actions (documentActions, + document_entries, + n_document_entries, + window); + + /* + documents_list_menu_actions = + gtk_action_group_new (ACTION_GROUP_DOCUMENTS_LIST_MENU); + gtk_action_group_set_translation_domain (documents_list_menu_actions, + GETTEXT_PACKAGE); + */ + + ui = gtk_ui_manager_new (); + + gtk_ui_manager_insert_action_group (ui, staticActions, 0); + gtk_ui_manager_insert_action_group (ui, documentActions, 1); + //gtk_ui_manager_insert_action_group (ui, documents_list_menu_actions, 3); + + gtk_window_add_accel_group ([window gtkWindow], gtk_ui_manager_get_accel_group (ui)); + + if (!gtk_ui_manager_add_ui_from_string (ui, ui_info, -1, &error)) + { + g_message ("Building menus failed: %s", error->message); + g_error_free (error); + return NULL; + } + + /* Set custom images for tool mode buttons */ + set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/SelectMode")), &select_rectangular); + set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/CreateNodeMode")), &draw_ellipse); + set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/DrawEdgeMode")), &draw_path); + set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/BoundingBoxMode")), &transform_crop_and_resize); + set_tool_button_image (GTK_TOOL_BUTTON (gtk_ui_manager_get_widget (ui, "/ToolBar/HandMode")), &transform_move); + + /* Save the undo and redo actions so they can be updated */ + undoAction = gtk_action_group_get_action (documentActions, "Undo"); + redoAction = gtk_action_group_get_action (documentActions, "Redo"); + pasteAction = gtk_action_group_get_action (documentActions, "Paste"); + + /* Recent items */ + GtkWidget *recentMenu = create_recent_chooser_menu(); + GtkMenuItem *recentMenuItem = GTK_MENU_ITEM (gtk_ui_manager_get_widget (ui, "/MenuBar/FileMenu/OpenRecent")); + gtk_menu_item_set_submenu (recentMenuItem, recentMenu); + g_signal_connect (recentMenu, "item-activated", G_CALLBACK (recent_chooser_item_activated_cb), window); + + nodeSelBasedActionCount = 4; + nodeSelBasedActions = g_new (GtkAction*, nodeSelBasedActionCount); + nodeSelBasedActions[0] = gtk_action_group_get_action (documentActions, "Cut"); + nodeSelBasedActions[1] = gtk_action_group_get_action (documentActions, "Copy"); + nodeSelBasedActions[2] = gtk_action_group_get_action (documentActions, "FlipHoriz"); + nodeSelBasedActions[3] = gtk_action_group_get_action (documentActions, "FlipVert"); + selBasedActionCount = 2; + selBasedActions = g_new (GtkAction*, selBasedActionCount); + selBasedActions[0] = gtk_action_group_get_action (documentActions, "Delete"); + selBasedActions[1] = gtk_action_group_get_action (documentActions, "DeselectAll"); + + Configuration *configFile = [window mainConfiguration]; + if ([configFile hasKey:@"toolbarStyle" inGroup:@"UI"]) { + int value = [configFile integerEntry:@"toolbarStyle" inGroup:@"UI"]; + gtk_radio_action_set_current_value ( + GTK_RADIO_ACTION (gtk_action_group_get_action (staticActions, "ToolbarIconsOnly")), + value); + } + + return self; +} + +- (GtkWidget*) menubar { + return gtk_ui_manager_get_widget (ui, "/MenuBar"); +} + +- (GtkWidget*) toolbar { + return gtk_ui_manager_get_widget (ui, "/ToolBar"); +} + +- (MainWindow*) mainWindow { + return mainWindow; +} + +- (void) setUndoActionEnabled:(BOOL)enabled { + gtk_action_set_sensitive (undoAction, enabled); +} + +- (void) setUndoActionDetail:(NSString*)detail { + gtk_action_set_detailed_label (undoAction, "_Undo", [detail UTF8String]); +} + +- (void) setRedoActionEnabled:(BOOL)enabled { + gtk_action_set_sensitive (redoAction, enabled); +} + +- (void) setRedoActionDetail:(NSString*)detail { + gtk_action_set_detailed_label (redoAction, "_Redo", [detail UTF8String]); +} + +- (GtkAction*) pasteAction { + return pasteAction; +} + +- (void) notifySelectionChanged:(PickSupport*)pickSupport { + BOOL hasSelectedNodes = [[pickSupport selectedNodes] count] > 0; + BOOL hasSelectedEdges = [[pickSupport selectedEdges] count] > 0; + for (int i = 0; i < nodeSelBasedActionCount; ++i) { + if (nodeSelBasedActions[i]) { + gtk_action_set_sensitive (nodeSelBasedActions[i], hasSelectedNodes); + } + } + for (int i = 0; i < selBasedActionCount; ++i) { + if (selBasedActions[i]) { + gtk_action_set_sensitive (selBasedActions[i], hasSelectedNodes || hasSelectedEdges); + } + } +} + +- (void) dealloc { + g_free (nodeSelBasedActions); + g_free (selBasedActions); + + [super dealloc]; +} + +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/NSError+Glib.h b/tikzit/src/linux/NSError+Glib.h new file mode 100644 index 0000000..137977e --- /dev/null +++ b/tikzit/src/linux/NSError+Glib.h @@ -0,0 +1,28 @@ +/* + * 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 <glib.h> + +@interface NSError(Glib) ++ (id) errorWithGError:(GError*)gerror; +@end + +void GErrorToNSError(GError *errorIn, NSError **errorOut); +void logGError (GError *error, NSString *message); + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NSError+Glib.m b/tikzit/src/linux/NSError+Glib.m new file mode 100644 index 0000000..f466d9e --- /dev/null +++ b/tikzit/src/linux/NSError+Glib.m @@ -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 "NSError+Glib.h" +#import "TZFoundation.h" + +@implementation NSError(Glib) ++ (id) errorWithGError:(GError*)gerror { + if (!gerror) + return nil; + + NSString *message = [NSString stringWithUTF8String:gerror->message]; + NSString *domain = [NSString stringWithUTF8String:g_quark_to_string(gerror->domain)]; + + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:domain code:gerror->code userInfo:errorDetail]; +} +@end + +void GErrorToNSError(GError *errorIn, NSError **errorOut) +{ + if (errorOut && errorIn) { + *errorOut = [NSError errorWithGError:errorIn]; + } +} + +void logGError (GError *error, NSString *message) { + if (message == nil) { + NSLog (@"%s", error->message); + } else { + NSLog (@"%@: %s", message, error->message); + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NSFileManager+Glib.h b/tikzit/src/linux/NSFileManager+Glib.h new file mode 100644 index 0000000..cb49fcb --- /dev/null +++ b/tikzit/src/linux/NSFileManager+Glib.h @@ -0,0 +1,31 @@ +/* + * 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> + +@interface NSFileManager(Glib) +/** + * Creates a directory in the system temp directory + */ +- (NSString*) createTempDirectoryWithError:(NSError**)error; +/** + * Creates a directory in the system temp directory + */ +- (NSString*) createTempDirectory; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NSFileManager+Glib.m b/tikzit/src/linux/NSFileManager+Glib.m new file mode 100644 index 0000000..b3e9de6 --- /dev/null +++ b/tikzit/src/linux/NSFileManager+Glib.m @@ -0,0 +1,55 @@ +/* + * 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 "NSFileManager+Glib.h" +#import "TZFoundation.h" +#import "mkdtemp.h" + +@implementation NSFileManager(Glib) + +- (NSString*) createTempDirectoryWithError:(NSError**)error { + NSString *result = nil; +#if GLIB_CHECK_VERSION (2, 30, 0) + GError *gerror = NULL; + gchar *dir = g_dir_make_tmp ("tikzitXXXXXX", &gerror); + GErrorToNSError (gerror, error); + if (dir) + result = [NSString stringWithGlibFilename:dir]; + g_free (dir); +#else +//#if (!GLIB_CHECK_VERSION (2, 26, 0)) +#define g_mkdtemp mkdtemp +//#endif + gchar *dir = g_build_filename (g_get_tmp_dir(), "tikzitXXXXXX", NULL); + gchar *rdir = g_mkdtemp (dir); + if (rdir) { + result = [NSString stringWithGlibFilename:dir]; + } else if (error) { + *error = [NSError errorWithLibcError:errno]; + } + g_free (dir); +#endif + return result; +} + +- (NSString*) createTempDirectory { + return [self createTempDirectoryWithError:NULL]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NSString+Glib.h b/tikzit/src/linux/NSString+Glib.h new file mode 100644 index 0000000..ac59833 --- /dev/null +++ b/tikzit/src/linux/NSString+Glib.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> +#import <glib.h> + +@interface NSString(Glib) +/** + * Initialise a string with a string in the GLib filename encoding + */ +- (id) initWithGlibFilename:(const gchar *)filename; +/** + * Create a string from a string in the GLib filename encoding + */ ++ (id) stringWithGlibFilename:(const gchar *)filename; +/** + * Get a copy of the string in GLib filename encoding. + * + * This will need to be freed with g_free. + */ +- (gchar*)glibFilename; +/** + * Get a copy of the string as a GLib URI + * + * This will need to be freed with g_free. + */ +- (gchar*)glibUriWithError:(NSError**)error; +/** + * Get a copy of the string as a GLib URI + * + * This will need to be freed with g_free. + */ +- (gchar*)glibUri; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NSString+Glib.m b/tikzit/src/linux/NSString+Glib.m new file mode 100644 index 0000000..b6dc765 --- /dev/null +++ b/tikzit/src/linux/NSString+Glib.m @@ -0,0 +1,96 @@ +/* + * 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 "NSString+Glib.h" +#import "TZFoundation.h" + +@implementation NSString(Glib) ++ (id) stringWithGlibFilename:(const gchar *)filename { + return [[[self alloc] initWithGlibFilename:filename] autorelease]; +} + +- (id) initWithGlibFilename:(const gchar *)filename { + if (self == nil) { + return nil; + } + + if (filename == NULL) { + [self release]; + return nil; + } + + GError *error = NULL; + gchar *utf8file = g_filename_to_utf8 (filename, -1, NULL, NULL, &error); + if (utf8file == NULL) { + if (error) + logGError (error, @"Failed to convert a GLib filename to UTF8"); + [self release]; + return nil; + } + + self = [self initWithUTF8String:utf8file]; + g_free (utf8file); + + return self; +} + +- (gchar*)glibFilenameWithError:(NSError**)error { + GError *gerror = NULL; + gchar *result = g_filename_from_utf8 ([self UTF8String], -1, NULL, NULL, &gerror); + GErrorToNSError (gerror, error); + if (gerror) { + logGError (gerror, @"Failed to convert a UTF8 string to a GLib filename"); + } + return result; +} + +- (gchar*)glibFilename { + return [self glibFilenameWithError:NULL]; +} + +- (gchar*)glibUriWithError:(NSError**)error { + gchar *filepath; + gchar *uri; + NSError *cause = nil; + + filepath = [self glibFilenameWithError:&cause]; + if (!filepath) { + if (error) { + NSString *message = [NSString stringWithFormat:@"Could not convert \"%@\" to the GLib filename encoding", self]; + *error = [NSError errorWithMessage:message code:TZ_ERR_OTHER cause:cause]; + } + return NULL; + } + + GError *gerror = NULL; + GError **gerrorptr = error ? &gerror : NULL; + uri = g_filename_to_uri (filepath, NULL, gerrorptr); + if (!uri && error) { + NSString *message = [NSString stringWithFormat:@"Could not convert \"%@\" to a GLib URI", self]; + *error = [NSError errorWithMessage:message code:TZ_ERR_BADFORMAT cause:[NSError errorWithGError:gerror]]; + } + g_free (filepath); + return uri; +} + +- (gchar*)glibUri { + return [self glibUriWithError:NULL]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Node+Render.h b/tikzit/src/linux/Node+Render.h new file mode 100644 index 0000000..49667e1 --- /dev/null +++ b/tikzit/src/linux/Node+Render.h @@ -0,0 +1,44 @@ +/* + * 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 "TZFoundation.h" +#import "Node.h" +#import "RenderContext.h" +#import "Surface.h" + +enum NodeState { + NodeNormal, + NodeSelected, + NodeHighlighted +}; + +@interface Node(Render) + +- (Transformer*) shapeTransformerForSurface:(id<Surface>)surface; +// the total rendered bounds, excluding label +- (NSRect) boundsOnSurface:(id<Surface>)surface; +- (NSRect) boundsWithLabelOnSurface:(id<Surface>)surface; +- (NSString*) renderedLabel; +- (NSSize) renderedLabelSizeInContext:(id<RenderContext>)context; +- (void) renderLabelToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context; +- (void) renderLabelAt:(NSPoint)point withContext:(id<RenderContext>)context; +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context state:(enum NodeState)state; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Node+Render.m b/tikzit/src/linux/Node+Render.m new file mode 100644 index 0000000..f168924 --- /dev/null +++ b/tikzit/src/linux/Node+Render.m @@ -0,0 +1,197 @@ +/* + * 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 "Node+Render.h" +#import "Shape.h" +#import "Shape+Render.h" +#import "ShapeNames.h" + +#define DEFAULT_STROKE_WIDTH 2.0f +#define MAX_LABEL_LENGTH 10 +#define LABEL_PADDING_X 2 +#define LABEL_PADDING_Y 2 + +@implementation Node (Render) + +- (Transformer*) shapeTransformerForSurface:(id<Surface>)surface { + Transformer *transformer = [[[surface transformer] copy] autorelease]; + NSPoint screenPos = [[surface transformer] toScreen:point]; + [transformer setOrigin:screenPos]; + CGFloat scale = [[surface transformer] scale]; + if (style) { + scale *= [style scale]; + } + [transformer setScale:scale]; + return transformer; +} + +- (Shape*) shape { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return [Shape shapeForName:SHAPE_CIRCLE]; + } +} + +- (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans { + float strokeThickness = style ? [style strokeThickness] : DEFAULT_STROKE_WIDTH; + NSRect screenBounds = [shapeTrans rectToScreen:[[self shape] boundingRect]]; + screenBounds = NSInsetRect(screenBounds, -strokeThickness, -strokeThickness); + return screenBounds; +} + +- (NSRect) boundsOnSurface:(id<Surface>)surface { + return [self boundsUsingShapeTransform:[self shapeTransformerForSurface:surface]]; +} + +- (NSRect) boundsWithLabelOnSurface:(id<Surface>)surface { + NSRect nodeBounds = [self boundsOnSurface:surface]; + NSRect labelRect = NSZeroRect; + if (![label isEqual:@""]) { + id<RenderContext> cr = [surface createRenderContext]; + labelRect.size = [self renderedLabelSizeInContext:cr]; + NSPoint nodePos = [[surface transformer] toScreen:point]; + labelRect.origin.x = nodePos.x - (labelRect.size.width / 2); + labelRect.origin.y = nodePos.y - (labelRect.size.height / 2); + } + return NSUnionRect(nodeBounds, labelRect); +} + +- (RColor) strokeColor { + if (style) { + return [[style strokeColorRGB] rColor]; + } else { + return MakeRColor (0.4, 0.4, 0.7, 0.8); + } +} + +- (RColor) fillColor { + if (style) { + return [[style fillColorRGB] rColor]; + } else { + return MakeRColor (0.4, 0.4, 0.7, 0.3); + } +} + +- (NSString*) renderedLabel { + NSString *r_label = [label stringByExpandingLatexConstants]; + if ([r_label length] > MAX_LABEL_LENGTH) { + r_label = [[[r_label substringToIndex:MAX_LABEL_LENGTH-1] stringByTrimmingSpaces] stringByAppendingString:@"..."]; + } else { + r_label = [r_label stringByTrimmingSpaces]; + } + return r_label; +} + +- (NSSize) renderedLabelSizeInContext:(id<RenderContext>)context { + NSSize result = {0, 0}; + if (![label isEqual:@""]) { + NSString *r_label = [self renderedLabel]; + + id<TextLayout> layout = [context layoutText:r_label withSize:9]; + + result = [layout size]; + result.width += LABEL_PADDING_X; + result.height += LABEL_PADDING_Y; + } + return result; +} + +- (void) renderLabelToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context { + [self renderLabelAt:[[surface transformer] toScreen:point] withContext:context]; +} + +- (void) renderLabelAt:(NSPoint)p withContext:(id<RenderContext>)context { + // draw latex code overlayed on node + if (![label isEqual:@""]) { + [context saveState]; + + NSString *r_label = [self renderedLabel]; + id<TextLayout> layout = [context layoutText:r_label withSize:9]; + + NSSize labelSize = [layout size]; + + NSRect textBounds = NSMakeRect (p.x - labelSize.width/2, + p.y - labelSize.height/2, + labelSize.width, + labelSize.height); + NSRect backRect = NSInsetRect (textBounds, -LABEL_PADDING_X, -LABEL_PADDING_Y); + + [context startPath]; + [context setLineWidth:1.0]; + [context rect:backRect]; + RColor fColor = MakeRColor (1.0, 1.0, 0.5, 0.7); + RColor sColor = MakeRColor (0.5, 0.0, 0.0, 0.7); + [context strokePathWithColor:sColor andFillWithColor:fColor]; + + [layout showTextAt:textBounds.origin withColor:BlackRColor]; + + [context restoreState]; + } +} + +- (void) renderToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context state:(enum NodeState)state { + Transformer *shapeTrans = [self shapeTransformerForSurface:surface]; + float strokeThickness = style ? [style strokeThickness] : DEFAULT_STROKE_WIDTH; + + [context saveState]; + + [[self shape] drawPathWithTransform:shapeTrans andContext:context]; + + [context setLineWidth:strokeThickness]; + if (!style) { + [context setLineDash:3.0]; + } + [context strokePathWithColor:[self strokeColor] andFillWithColor:[self fillColor]]; + + if (state != NodeNormal) { + [context setLineWidth:strokeThickness + 4.0]; + [context setLineDash:0.0]; + float alpha = 0.0f; + if (state == NodeSelected) + alpha = 0.5f; + else if (state == NodeHighlighted) + alpha = 0.25f; + RColor selectionColor = MakeSolidRColor(0.61f, 0.735f, 1.0f); + + [[self shape] drawPathWithTransform:shapeTrans andContext:context]; + [context strokePathWithColor:selectionColor andFillWithColor:selectionColor usingAlpha:alpha]; + } + + [context restoreState]; + [self renderLabelToSurface:surface withContext:context]; +} + +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface { + Transformer *shapeTrans = [self shapeTransformerForSurface:surface]; + + NSRect screenBounds = [self boundsUsingShapeTransform:shapeTrans]; + if (!NSPointInRect(p, screenBounds)) { + return NO; + } + + float strokeThickness = style ? [style strokeThickness] : DEFAULT_STROKE_WIDTH; + id<RenderContext> ctx = [surface createRenderContext]; + [ctx setLineWidth:strokeThickness]; + [[self shape] drawPathWithTransform:shapeTrans andContext:ctx]; + return [ctx strokeIncludesPoint:p] || [ctx fillIncludesPoint:p]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyle+Gtk.h b/tikzit/src/linux/NodeStyle+Gtk.h new file mode 100644 index 0000000..4fa5edd --- /dev/null +++ b/tikzit/src/linux/NodeStyle+Gtk.h @@ -0,0 +1,31 @@ +/* + * 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 "TZFoundation.h" +#import "NodeStyle.h" +#import <gtk/gtk.h> + +@interface NodeStyle (Gtk) + +- (GdkColor) strokeColor; +- (void) setStrokeColor:(GdkColor)color; +- (GdkColor) fillColor; +- (void) setFillColor:(GdkColor)color; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyle+Gtk.m b/tikzit/src/linux/NodeStyle+Gtk.m new file mode 100644 index 0000000..1954def --- /dev/null +++ b/tikzit/src/linux/NodeStyle+Gtk.m @@ -0,0 +1,41 @@ +/* + * 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 "NodeStyle+Gtk.h" +#import "ColorRGB+Gtk.h" + +@implementation NodeStyle (Gtk) + +- (GdkColor) strokeColor { + return [[self strokeColorRGB] gdkColor]; +} + +- (void) setStrokeColor:(GdkColor)color { + [self setStrokeColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +- (GdkColor) fillColor { + return [[self fillColorRGB] gdkColor]; +} + +- (void) setFillColor:(GdkColor)color { + [self setFillColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyle+Render.h b/tikzit/src/linux/NodeStyle+Render.h new file mode 100644 index 0000000..00edd27 --- /dev/null +++ b/tikzit/src/linux/NodeStyle+Render.h @@ -0,0 +1,30 @@ +/* + * 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 "TZFoundation.h" +#import "NodeStyle.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface NodeStyle (Render) + +- (void) renderToSurface:(id<Surface>)surface withContext:(id<RenderContext>)context at:(NSPoint)p; +- (BOOL) hitByPoint:(NSPoint)p onSurface:(id<Surface>)surface; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyle+Storage.h b/tikzit/src/linux/NodeStyle+Storage.h new file mode 100644 index 0000000..7649414 --- /dev/null +++ b/tikzit/src/linux/NodeStyle+Storage.h @@ -0,0 +1,29 @@ +/* + * 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 "TZFoundation.h" +#import "NodeStyle.h" +#import "Configuration.h" + +@interface NodeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyle+Storage.m b/tikzit/src/linux/NodeStyle+Storage.m new file mode 100644 index 0000000..088b062 --- /dev/null +++ b/tikzit/src/linux/NodeStyle+Storage.m @@ -0,0 +1,62 @@ +/* + * 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 "NodeStyle+Storage.h" +#import "ColorRGB+IntegerListStorage.h" + +@implementation NodeStyle (Storage) + +- (id) initFromConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + self = [self init]; + + if (self) { + [self setName:[configFile stringEntry:@"Name" inGroup:groupName withDefault:name]]; + [self setCategory:[configFile stringEntry:@"Category" inGroup:groupName withDefault:category]]; + [self setShapeName:[configFile stringEntry:@"ShapeName" inGroup:groupName withDefault:shapeName]]; + [self setScale:[configFile doubleEntry:@"Scale" inGroup:groupName withDefault:scale]]; + [self setStrokeThickness:[configFile integerEntry:@"StrokeThickness" + inGroup:groupName + withDefault:strokeThickness]]; + [self setStrokeColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"StrokeColor" + inGroup:groupName + withDefault:[strokeColorRGB valueList]]]]; + [self setFillColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"FillColor" + inGroup:groupName + withDefault:[fillColorRGB valueList]]]]; + } + + return self; +} + +- (void) storeToConfigurationGroup:(NSString*)groupName config:(Configuration*)configFile { + [configFile setStringEntry:@"Name" inGroup:groupName value:[self name]]; + [configFile setStringEntry:@"Category" inGroup:groupName value:[self category]]; + [configFile setStringEntry:@"ShapeName" inGroup:groupName value:[self shapeName]]; + [configFile setDoubleEntry:@"Scale" inGroup:groupName value:[self scale]]; + [configFile setIntegerEntry:@"StrokeThickness" inGroup:groupName value:[self strokeThickness]]; + [configFile setIntegerListEntry:@"StrokeColor" inGroup:groupName value:[[self strokeColorRGB] valueList]]; + [configFile setIntegerListEntry:@"FillColor" inGroup:groupName value:[[self fillColorRGB] valueList]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyleEditor.h b/tikzit/src/linux/NodeStyleEditor.h new file mode 100644 index 0000000..67219e8 --- /dev/null +++ b/tikzit/src/linux/NodeStyleEditor.h @@ -0,0 +1,43 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +@class NodeStyle; + +@interface NodeStyleEditor: NSObject { + NodeStyle *style; + GtkTable *table; + GtkEntry *nameEdit; + GtkComboBox *shapeNameCombo; + GtkColorButton *strokeColorButton; + GtkWidget *makeStrokeTexSafeButton; + GtkColorButton *fillColorButton; + GtkWidget *makeFillTexSafeButton; + GtkAdjustment *scaleAdj; + BOOL blockSignals; +} + +@property (retain) NodeStyle *style; +@property (readonly) GtkWidget *widget; + +- (id) init; + +@end + +// vim:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyleEditor.m b/tikzit/src/linux/NodeStyleEditor.m new file mode 100644 index 0000000..38f78ca --- /dev/null +++ b/tikzit/src/linux/NodeStyleEditor.m @@ -0,0 +1,469 @@ +/* + * 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 "NodeStyleEditor.h" +#import "NodeStyle.h" +#import "NodeStyle+Gtk.h" +#import "Shape.h" + +static const guint row_count = 5; + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void style_name_edit_changed_cb (GtkEditable *widget, NodeStyleEditor *editor); +static void style_shape_combo_changed_cb (GtkComboBox *widget, NodeStyleEditor *editor); +static void stroke_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor); +static void fill_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor); +static void make_stroke_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor); +static void make_fill_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor); +static void scale_adjustment_changed_cb (GtkAdjustment *widget, NodeStyleEditor *editor); +// }}} +// {{{ Notifications + +@interface NodeStyleEditor (Notifications) +- (void) shapeDictionaryReplaced:(NSNotification*)n; +- (void) nameChangedTo:(NSString*)value; +- (void) shapeNameChangedTo:(NSString*)value; +- (void) strokeColorChangedTo:(GdkColor)value; +- (void) makeStrokeColorTexSafe; +- (void) fillColorChangedTo:(GdkColor)value; +- (void) makeFillColorTexSafe; +- (void) scaleChangedTo:(double)value; +@end + +// }}} +// {{{ Private + +@interface NodeStyleEditor (Private) +- (void) loadShapeNames; +- (void) setActiveShapeName:(NSString*)name; +@end + +// }}} +// }}} +// {{{ API + +@implementation NodeStyleEditor + +- (void) _addWidget:(GtkWidget*)w withLabel:(gchar *)label atRow:(guint)row { + NSAssert(row < row_count, @"row_count is wrong!"); + + GtkWidget *l = gtk_label_new (label); + gtk_widget_show (l); + gtk_widget_show (w); + + gtk_table_attach (table, l, + 0, 1, row, row+1, // l, r, t, b + GTK_FILL, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 5, // x padding + 0); // y padding + + gtk_table_attach (table, w, + 1, 2, row, row+1, // l, r, t, b + GTK_FILL | GTK_EXPAND, // x opts + GTK_FILL | GTK_EXPAND, // y opts + 0, // x padding + 0); // y padding +} + +- (GtkWidget*) _createMakeColorTexSafeButton:(NSString*)type { + GtkWidget *b = gtk_button_new (); + GtkWidget *icon = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (icon); + gtk_container_add (GTK_CONTAINER (b), icon); + NSString *ttip = [NSString stringWithFormat:@"The %@ colour is not a predefined TeX colour.\nClick here to choose the nearest TeX-safe colour.", type]; + gtk_widget_set_tooltip_text (b, [ttip UTF8String]); + return b; +} + +- (id) init { + self = [super init]; + + if (self != nil) { + style = nil; + table = GTK_TABLE (gtk_table_new (row_count, 2, FALSE)); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + blockSignals = NO; + + /** + * Name + */ + nameEdit = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (nameEdit); + [self _addWidget:GTK_WIDGET (nameEdit) + withLabel:"Name" + atRow:0]; + g_signal_connect (G_OBJECT (nameEdit), + "changed", + G_CALLBACK (style_name_edit_changed_cb), + self); + + + /** + * Shape + */ + GtkListStore *store = gtk_list_store_new (1, G_TYPE_STRING); + shapeNameCombo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + GtkCellRenderer *cellRend = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (shapeNameCombo), + cellRend, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (shapeNameCombo), cellRend, "text", 0); + g_object_ref_sink (shapeNameCombo); + [self _addWidget:GTK_WIDGET (shapeNameCombo) + withLabel:"Shape" + atRow:1]; + g_signal_connect (G_OBJECT (shapeNameCombo), + "changed", + G_CALLBACK (style_shape_combo_changed_cb), + self); + + + /** + * Stroke colour + */ + GtkWidget *strokeBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:strokeBox + withLabel:"Stroke colour" + atRow:2]; + strokeColorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (strokeColorButton); + gtk_widget_show (GTK_WIDGET (strokeColorButton)); + gtk_box_pack_start (GTK_BOX (strokeBox), GTK_WIDGET (strokeColorButton), + FALSE, FALSE, 0); + makeStrokeTexSafeButton = [self _createMakeColorTexSafeButton:@"stroke"]; + g_object_ref_sink (makeStrokeTexSafeButton); + gtk_box_pack_start (GTK_BOX (strokeBox), makeStrokeTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (strokeColorButton), + "color-set", + G_CALLBACK (stroke_color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeStrokeTexSafeButton), + "clicked", + G_CALLBACK (make_stroke_safe_button_clicked_cb), + self); + + + /** + * Fill colour + */ + GtkWidget *fillBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:fillBox + withLabel:"Fill colour" + atRow:3]; + fillColorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (fillColorButton); + gtk_widget_show (GTK_WIDGET (fillColorButton)); + gtk_box_pack_start (GTK_BOX (fillBox), GTK_WIDGET (fillColorButton), + FALSE, FALSE, 0); + makeFillTexSafeButton = [self _createMakeColorTexSafeButton:@"fill"]; + g_object_ref_sink (makeFillTexSafeButton); + gtk_box_pack_start (GTK_BOX (fillBox), makeFillTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (fillColorButton), + "color-set", + G_CALLBACK (fill_color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeFillTexSafeButton), + "clicked", + G_CALLBACK (make_fill_safe_button_clicked_cb), + self); + + + /** + * Scale + */ + scaleAdj = GTK_ADJUSTMENT (gtk_adjustment_new ( + 1.0, // value + 0.0, // lower + 50.0, // upper + 0.20, // step + 1.0, // page + 0.0)); // (irrelevant) + g_object_ref_sink (scaleAdj); + GtkWidget *scaleSpin = gtk_spin_button_new (scaleAdj, 0.0, 2); + [self _addWidget:scaleSpin + withLabel:"Scale" + atRow:4]; + g_signal_connect (G_OBJECT (scaleAdj), + "value-changed", + G_CALLBACK (scale_adjustment_changed_cb), + self); + + [self loadShapeNames]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nameEdit); + g_object_unref (shapeNameCombo); + g_object_unref (strokeColorButton); + g_object_unref (makeStrokeTexSafeButton); + g_object_unref (fillColorButton); + g_object_unref (makeFillTexSafeButton); + g_object_unref (scaleAdj); + g_object_unref (table); + [style release]; + + [super dealloc]; +} + +- (NodeStyle*) style { + return style; +} + +- (void) setStyle:(NodeStyle*)s { + blockSignals = YES; + NodeStyle *oldStyle = style; + style = [s retain]; + + if (style != nil) { + gtk_widget_set_sensitive (GTK_WIDGET (table), TRUE); + + gtk_entry_set_text(nameEdit, [[style name] UTF8String]); + + [self setActiveShapeName:[style shapeName]]; + + GdkColor c = [style strokeColor]; + gtk_color_button_set_color(strokeColorButton, &c); + + gtk_widget_set_visible (makeStrokeTexSafeButton, ([[style strokeColorRGB] name] == nil)); + + c = [style fillColor]; + gtk_color_button_set_color(fillColorButton, &c); + + gtk_widget_set_visible (makeFillTexSafeButton, ([[style fillColorRGB] name] == nil)); + + gtk_adjustment_set_value(scaleAdj, [style scale]); + } else { + gtk_entry_set_text(nameEdit, ""); + [self setActiveShapeName:nil]; + gtk_widget_set_visible (makeStrokeTexSafeButton, FALSE); + gtk_widget_set_visible (makeFillTexSafeButton, FALSE); + gtk_adjustment_set_value(scaleAdj, 1.0); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + } + + [oldStyle release]; + blockSignals = NO; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (table); +} + +@end + +// }}} +// {{{ Notifications + +@implementation NodeStyleEditor (Notifications) +- (void) shapeDictionaryReplaced:(NSNotification*)n { + blockSignals = YES; + + [self loadShapeNames]; + [self setActiveShapeName:[style shapeName]]; + + blockSignals = NO; +} + +- (void) nameChangedTo:(NSString*)value { + [style setName:value]; +} + +- (void) shapeNameChangedTo:(NSString*)value { + [style setShapeName:value]; +} + +- (void) strokeColorChangedTo:(GdkColor)value { + [style setStrokeColor:value]; + gtk_widget_set_visible (makeStrokeTexSafeButton, + [[style strokeColorRGB] name] == nil); +} + +- (void) makeStrokeColorTexSafe { + if (style != nil) { + [[style strokeColorRGB] setToClosestHashed]; + GdkColor color = [style strokeColor]; + gtk_color_button_set_color(strokeColorButton, &color); + gtk_widget_set_visible (makeStrokeTexSafeButton, FALSE); + } +} + +- (void) fillColorChangedTo:(GdkColor)value { + [style setFillColor:value]; + gtk_widget_set_visible (makeFillTexSafeButton, + [[style fillColorRGB] name] == nil); +} + +- (void) makeFillColorTexSafe { + if (style != nil) { + [[style fillColorRGB] setToClosestHashed]; + GdkColor color = [style fillColor]; + gtk_color_button_set_color(fillColorButton, &color); + gtk_widget_set_visible (makeFillTexSafeButton, FALSE); + } +} + +- (void) scaleChangedTo:(double)value { + [style setScale:value]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStyleEditor (Private) +- (BOOL) signalsBlocked { return blockSignals; } + +- (void) loadShapeNames { + blockSignals = YES; + + gtk_combo_box_set_active (shapeNameCombo, -1); + + GtkListStore *list = GTK_LIST_STORE (gtk_combo_box_get_model (shapeNameCombo)); + gtk_list_store_clear (list); + + NSEnumerator *en = [[Shape shapeDictionary] keyEnumerator]; + NSString *shapeName; + GtkTreeIter iter; + while ((shapeName = [en nextObject]) != nil) { + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, 0, [shapeName UTF8String], -1); + } + + blockSignals = NO; +} + +- (void) setActiveShapeName:(NSString*)name { + if (name == nil) { + gtk_combo_box_set_active (shapeNameCombo, -1); + return; + } + const gchar *shapeName = [name UTF8String]; + + GtkTreeModel *model = gtk_combo_box_get_model (shapeNameCombo); + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + gchar *rowShapeName; + gtk_tree_model_get (model, &iter, 0, &rowShapeName, -1); + if (g_strcmp0 (shapeName, rowShapeName) == 0) { + gtk_combo_box_set_active_iter (shapeNameCombo, &iter); + g_free (rowShapeName); + return; + } + g_free (rowShapeName); + } while (gtk_tree_model_iter_next (model, &iter)); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void style_name_edit_changed_cb (GtkEditable *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + const gchar *contents = gtk_entry_get_text (GTK_ENTRY (widget)); + [editor nameChangedTo:[NSString stringWithUTF8String:contents]]; + + [pool drain]; +} + +static void style_shape_combo_changed_cb (GtkComboBox *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + gchar *shapeName = NULL; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, 0, &shapeName, -1); + [editor shapeNameChangedTo:[NSString stringWithUTF8String:shapeName]]; + g_free (shapeName); + + [pool drain]; +} + +static void stroke_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor strokeColorChangedTo:color]; + + [pool drain]; +} + +static void fill_color_changed_cb (GtkColorButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor fillColorChangedTo:color]; + + [pool drain]; +} + +static void make_stroke_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeStrokeColorTexSafe]; + [pool drain]; +} + +static void make_fill_safe_button_clicked_cb (GtkButton *widget, NodeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeFillColorTexSafe]; + [pool drain]; +} + +static void scale_adjustment_changed_cb (GtkAdjustment *adj, NodeStyleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor scaleChangedTo:gtk_adjustment_get_value (adj)]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:noet:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/NodeStyleSelector.h b/tikzit/src/linux/NodeStyleSelector.h new file mode 100644 index 0000000..c6c8833 --- /dev/null +++ b/tikzit/src/linux/NodeStyleSelector.h @@ -0,0 +1,77 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "StyleManager.h" + +@interface NodeStyleSelector: NSObject { + GtkListStore *store; + GtkIconView *view; + StyleManager *styleManager; + BOOL linkedToActiveStyle; + BOOL suppressSetActiveStyle; +} + +/*! + @property widget + @brief The GTK widget + */ +@property (readonly) GtkWidget *widget; + +/*! + @property manager + @brief The StyleManager to use. Default is [StyleManager manager] + */ +@property (retain) StyleManager *styleManager; + +/*! + @property linkedToActiveStyles + @brief Whether the current selection should be the same as the + style manager's active style + */ +@property (getter=isLinkedToActiveStyle) BOOL linkedToActiveStyle; + +/*! + @property selectedStyle + @brief The selected style. If linkedToActiveStyle is YES, this + will be the same as [manager activeStyle]. + + When this changes, a SelectedStyleChanged notification will be posted + */ +@property (assign) NodeStyle *selectedStyle; + +/*! + * Initialise with the default style manager and a new icon view widget + */ +- (id) init; +/*! + * Initialise with the default style manager and the given icon view widget + */ +- (id) initWithIconView:(GtkIconView*)view; +/*! + * Initialise with the given style manager and a new icon view widget + */ +- (id) initWithStyleManager:(StyleManager*)m; +/*! + * Initialise with the given style manager and icon view widget + */ +- (id) initWithIconView:(GtkIconView*)view andStyleManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStyleSelector.m b/tikzit/src/linux/NodeStyleSelector.m new file mode 100644 index 0000000..5539b93 --- /dev/null +++ b/tikzit/src/linux/NodeStyleSelector.m @@ -0,0 +1,429 @@ +/* + * 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 "NodeStyleSelector.h" + +#import "CairoRenderContext.h" +#import "Shape.h" +#import "Shape+Render.h" +#import "ShapeNames.h" +#import "StyleManager.h" + +#import "gdk-pixbuf/gdk-pixbuf.h" + +// {{{ Signals +static void selection_changed_cb (GtkIconView *widget, NodeStyleSelector *mgr); +// }}} + +enum { + STYLES_NAME_COL = 0, + STYLES_ICON_COL, + STYLES_PTR_COL, + STYLES_N_COLS +}; + +@interface NodeStyleSelector (Notifications) +- (void) stylesReplaced:(NSNotification*)notification; +- (void) styleAdded:(NSNotification*)notification; +- (void) styleRemoved:(NSNotification*)notification; +- (void) activeStyleChanged:(NSNotification*)notification; +- (void) stylePropertyChanged:(NSNotification*)notification; +- (void) shapeDictionaryReplaced:(NSNotification*)n; +- (void) selectionChanged; +@end + +@interface NodeStyleSelector (Private) +- (cairo_surface_t*) createNodeIconSurface; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style; +- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) addStyle:(NodeStyle*)style; +- (void) postSelectedStyleChanged; +- (void) reloadStyles; +@end + +// {{{ API +@implementation NodeStyleSelector + +- (id) init { + self = [self initWithStyleManager:[StyleManager manager]]; + return self; +} + +- (id) initWithIconView:(GtkIconView*)view { + self = [self initWithIconView:GTK_ICON_VIEW (gtk_icon_view_new ()) andStyleManager:[StyleManager manager]]; + return self; +} + +- (id) initWithStyleManager:(StyleManager*)m { + self = [self initWithIconView:GTK_ICON_VIEW (gtk_icon_view_new ()) andStyleManager:m]; + return self; +} + +- (id) initWithIconView:(GtkIconView*)v andStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + styleManager = nil; + linkedToActiveStyle = YES; + + store = gtk_list_store_new (STYLES_N_COLS, + G_TYPE_STRING, + GDK_TYPE_PIXBUF, + G_TYPE_POINTER); + g_object_ref (store); + + view = v; + g_object_ref (view); + + gtk_icon_view_set_model (view, GTK_TREE_MODEL (store)); + gtk_icon_view_set_pixbuf_column (view, STYLES_ICON_COL); + gtk_icon_view_set_tooltip_column (view, STYLES_NAME_COL); + gtk_icon_view_set_selection_mode (view, GTK_SELECTION_SINGLE); + + g_signal_connect (G_OBJECT (view), + "selection-changed", + G_CALLBACK (selection_changed_cb), + self); + + [self setStyleManager:m]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(stylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + + return self; +} + +- (void) clearModel { + [self setSelectedStyle:nil]; + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1); + [rowStyle release]; + } while (gtk_tree_model_iter_next (model, &row)); + } + gtk_list_store_clear (store); +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)m { + if (m == nil) { + [NSException raise:NSInvalidArgumentException format:@"Style manager cannot be nil"]; + } + [m retain]; + + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:styleManager]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(stylesReplaced:) + name:@"NodeStylesReplaced" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(styleAdded:) + name:@"NodeStyleAdded" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(styleRemoved:) + name:@"NodeStyleRemoved" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(activeStyleChanged:) + name:@"ActiveNodeStyleChanged" + object:m]; + + [styleManager release]; + styleManager = m; + + [self reloadStyles]; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (view); +} + +- (BOOL) isLinkedToActiveStyle { + return linkedToActiveStyle; +} + +- (void) setLinkedToActiveStyle:(BOOL)linked { + linkedToActiveStyle = linked; + if (linkedToActiveStyle) { + NodeStyle *style = [self selectedStyle]; + if ([styleManager activeNodeStyle] != style) { + [self setSelectedStyle:[styleManager activeNodeStyle]]; + } + } +} + +- (NodeStyle*) selectedStyle { + GList *list = gtk_icon_view_get_selected_items (view); + if (!list) { + return nil; + } + if (list->next != NULL) { + NSLog(@"Multiple selected items in NodeStyleSelector!"); + } + + GtkTreePath *path = (GtkTreePath*) list->data; + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + NodeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, STYLES_PTR_COL, &style, -1); + + g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL); + g_list_free (list); + + return style; +} + +- (void) setSelectedStyle:(NodeStyle*)style { + if (style == nil) { + gtk_icon_view_unselect_all (view); + return; + } + + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (m, &row, STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_icon_view_unselect_all (view); + GtkTreePath *path = gtk_tree_model_get_path (m, &row); + gtk_icon_view_select_path (view, path); + gtk_tree_path_free (path); + // styleManager.activeStyle will be updated by the GTK+ callback + return; + } + } while (gtk_tree_model_iter_next (m, &row)); + } +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + g_object_unref (view); + [self clearModel]; + g_object_unref (store); + [styleManager release]; + + [super dealloc]; +} + +@end +// }}} + +// {{{ Notifications +@implementation NodeStyleSelector (Notifications) + +- (void) stylesReplaced:(NSNotification*)notification { + [self reloadStyles]; +} + +- (void) styleAdded:(NSNotification*)notification { + [self addStyle:[[notification userInfo] objectForKey:@"style"]]; +} + +- (void) styleRemoved:(NSNotification*)notification { + NodeStyle *style = [[notification userInfo] objectForKey:@"style"]; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_list_store_remove (store, &row); + [rowStyle release]; + return; + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) activeStyleChanged:(NSNotification*)notification { + if (linkedToActiveStyle) { + NodeStyle *style = [self selectedStyle]; + if ([styleManager activeNodeStyle] != style) { + [self setSelectedStyle:[styleManager activeNodeStyle]]; + } + } +} + +- (void) stylePropertyChanged:(NSNotification*)notification { + NodeStyle *style = [notification object]; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + NodeStyle *rowStyle; + gtk_tree_model_get (model, &row, STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + if ([@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + gtk_list_store_set (store, &row, STYLES_NAME_COL, [[style name] UTF8String], -1); + } else if (![@"scale" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style]; + gtk_list_store_set (store, &row, STYLES_ICON_COL, pixbuf, -1); + gdk_pixbuf_unref (pixbuf); + } + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) shapeDictionaryReplaced:(NSNotification*)n { + [self reloadStyles]; +} + +- (void) selectionChanged { + if (linkedToActiveStyle) { + NodeStyle *style = [self selectedStyle]; + if ([styleManager activeNodeStyle] != style) { + [styleManager setActiveNodeStyle:style]; + } + } + [self postSelectedStyleChanged]; +} +@end +// }}} + +// {{{ Private +@implementation NodeStyleSelector (Private) +- (cairo_surface_t*) createNodeIconSurface { + return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 24, 24); +} + +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style { + cairo_surface_t *surface = [self createNodeIconSurface]; + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); + return pixbuf; +} + +// Bring on GTK+3 and gdk_pixbuf_get_from_surface() +- (GdkPixbuf*) pixbufFromSurface:(cairo_surface_t*)surface { + cairo_surface_flush (surface); + + int width = cairo_image_surface_get_width (surface); + int height = cairo_image_surface_get_height (surface); + int stride = cairo_image_surface_get_stride (surface); + unsigned char *data = cairo_image_surface_get_data (surface); + + GdkPixbuf *pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + width, + height); + unsigned char *pbdata = gdk_pixbuf_get_pixels (pixbuf); + int pbstride = gdk_pixbuf_get_rowstride (pixbuf); + + for (int y = 0; y < height; ++y) { + uint32_t *line = (uint32_t*)(data + y*stride); + unsigned char *pbline = pbdata + (y*pbstride); + for (int x = 0; x < width; ++x) { + uint32_t pixel = *(line + x); + unsigned char *pbpixel = pbline + (x*4); + // NB: We should un-pre-mult the alpha here. + // However, in our world, alpha is always + // on or off, so it doesn't really matter + pbpixel[3] = ((pixel & 0xff000000) >> 24); + pbpixel[0] = ((pixel & 0x00ff0000) >> 16); + pbpixel[1] = ((pixel & 0x0000ff00) >> 8); + pbpixel[2] = (pixel & 0x000000ff); + } + } + + return pixbuf; +} + +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface { + Shape *shape = [Shape shapeForName:[style shapeName]]; + + int width = cairo_image_surface_get_width (surface); + int height = cairo_image_surface_get_height (surface); + NSRect pixbufBounds = NSMakeRect(0.0, 0.0, width, height); + const CGFloat lineWidth = [style strokeThickness]; + Transformer *shapeTrans = [Transformer transformerToFit:[shape boundingRect] + intoScreenRect:NSInsetRect(pixbufBounds, lineWidth, lineWidth) + flippedAboutXAxis:YES]; + + CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface]; + [context clearSurface]; + [shape drawPathWithTransform:shapeTrans andContext:context]; + [context setLineWidth:lineWidth]; + [context strokePathWithColor:[[style strokeColorRGB] rColor] + andFillWithColor:[[style fillColorRGB] rColor]]; + [context release]; + + return [self pixbufFromSurface:surface]; +} + +- (void) addStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style usingSurface:surface]; + gtk_list_store_set (store, &iter, + STYLES_NAME_COL, [[style name] UTF8String], + STYLES_ICON_COL, pixbuf, + STYLES_PTR_COL, (gpointer)[style retain], + -1); + gdk_pixbuf_unref (pixbuf); +} + +- (void) addStyle:(NodeStyle*)style { + cairo_surface_t *surface = [self createNodeIconSurface]; + [self addStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); +} + +- (void) postSelectedStyleChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectedStyleChanged" object:self]; +} + +- (void) reloadStyles { + [self clearModel]; + for (NodeStyle *style in [styleManager nodeStyles]) { + [self addStyle:style]; + } +} +@end +// }}} + +// {{{ GTK+ callbacks +static void selection_changed_cb (GtkIconView *view, NodeStyleSelector *mgr) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [mgr selectionChanged]; + [pool drain]; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStylesPalette.h b/tikzit/src/linux/NodeStylesPalette.h new file mode 100644 index 0000000..dfecc17 --- /dev/null +++ b/tikzit/src/linux/NodeStylesPalette.h @@ -0,0 +1,46 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +@class StyleManager; +@class NodeStyleSelector; +@class NodeStyleEditor; +@class TikzDocument; + +@interface NodeStylesPalette: NSObject { + TikzDocument *document; + NodeStyleSelector *selector; + NodeStyleEditor *editor; + + GtkWidget *palette; + + GtkWidget *removeStyleButton; + GtkWidget *applyStyleButton; + GtkWidget *clearStyleButton; +} + +@property (retain) StyleManager *styleManager; +@property (retain) TikzDocument *document; +@property (readonly) GtkWidget *widget; + +- (id) initWithManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit/src/linux/NodeStylesPalette.m b/tikzit/src/linux/NodeStylesPalette.m new file mode 100644 index 0000000..18738f7 --- /dev/null +++ b/tikzit/src/linux/NodeStylesPalette.m @@ -0,0 +1,275 @@ +/* + * 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 "NodeStylesPalette.h" + +#import "NodeStyleSelector.h" +#import "NodeStyleEditor.h" +#import "StyleManager.h" +#import "TikzDocument.h" + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +static void remove_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +static void apply_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +static void clear_style_button_cb (GtkButton *widget, NodeStylesPalette *palette); +// }}} +// {{{ Notifications + +@interface NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification; +- (void) nodeSelectionChanged:(NSNotification*)n; +@end + +// }}} +// {{{ Private + +@interface NodeStylesPalette (Private) +- (void) updateButtonState; +- (void) removeSelectedStyle; +- (void) applySelectedStyle; +- (void) clearSelectedStyle; +@end + +// }}} +// }}} +// {{{ API + +@implementation NodeStylesPalette + +@synthesize widget=palette; + +- (id) init { + [self release]; + self = nil; + return nil; +} + +- (id) initWithManager:(StyleManager*)m { + self = [super init]; + + if (self) { + document = nil; + selector = [[NodeStyleSelector alloc] initWithStyleManager:m]; + editor = [[NodeStyleEditor alloc] init]; + + palette = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (palette); + + gtk_box_pack_start (GTK_BOX (palette), [editor widget], FALSE, FALSE, 0); + gtk_widget_show ([editor widget]); + gtk_box_pack_start (GTK_BOX (palette), [selector widget], TRUE, TRUE, 0); + gtk_widget_show ([selector widget]); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 5)); + gtk_box_pack_start (GTK_BOX (palette), GTK_WIDGET (buttonBox), FALSE, FALSE, 0); + + GtkBox *bbox1 = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (buttonBox, GTK_WIDGET (bbox1), FALSE, FALSE, 0); + + GtkWidget *addStyleButton = gtk_button_new (); + gtk_widget_set_tooltip_text (addStyleButton, "Add a new style"); + GtkWidget *addIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addStyleButton), addIcon); + gtk_box_pack_start (bbox1, addStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addStyleButton), + "clicked", + G_CALLBACK (add_style_button_cb), + self); + + removeStyleButton = gtk_button_new (); + g_object_ref_sink (removeStyleButton); + gtk_widget_set_tooltip_text (removeStyleButton, "Delete selected style"); + GtkWidget *removeIcon = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (removeStyleButton), removeIcon); + gtk_box_pack_start (bbox1, removeStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeStyleButton), + "clicked", + G_CALLBACK (remove_style_button_cb), + self); + + GtkBox *bbox2 = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (buttonBox, GTK_WIDGET (bbox2), FALSE, FALSE, 0); + + applyStyleButton = gtk_button_new_with_label ("Apply"); + g_object_ref_sink (applyStyleButton); + gtk_widget_set_tooltip_text (applyStyleButton, "Apply style to selected nodes"); + gtk_box_pack_start (bbox2, applyStyleButton, FALSE, FALSE, 5); + g_signal_connect (G_OBJECT (applyStyleButton), + "clicked", + G_CALLBACK (apply_style_button_cb), + self); + + clearStyleButton = gtk_button_new_with_label ("Clear"); + g_object_ref_sink (clearStyleButton); + gtk_widget_set_tooltip_text (clearStyleButton, "Clear style from selected nodes"); + gtk_box_pack_start (bbox2, clearStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (clearStyleButton), + "clicked", + G_CALLBACK (clear_style_button_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(selectedStyleChanged:) + name:@"SelectedStyleChanged" + object:selector]; + + [self updateButtonState]; + } + + return self; +} + +- (StyleManager*) styleManager { + return [selector styleManager]; +} + +- (void) setStyleManager:(StyleManager*)m { + [selector setStyleManager:m]; +} + +- (TikzDocument*) document { + return document; +} + +- (void) setDocument:(TikzDocument*)doc { + if (document != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:nil + object:[document pickSupport]]; + } + + [doc retain]; + [document release]; + document = doc; + + if (document != nil) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeSelectionChanged:) + name:@"NodeSelectionChanged" + object:[document pickSupport]]; + } +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [editor release]; + [selector release]; + [document release]; + g_object_unref (palette); + g_object_unref (removeStyleButton); + g_object_unref (applyStyleButton); + g_object_unref (clearStyleButton); + + [super dealloc]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification { + [editor setStyle:[selector selectedStyle]]; + [self updateButtonState]; +} + +- (void) nodeSelectionChanged:(NSNotification*)n { + [self updateButtonState]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStylesPalette (Private) +- (void) updateButtonState { + gboolean hasNodeSelection = [[[document pickSupport] selectedNodes] count] > 0; + gboolean hasStyleSelection = [selector selectedStyle] != nil; + + gtk_widget_set_sensitive (applyStyleButton, hasNodeSelection && hasStyleSelection); + gtk_widget_set_sensitive (clearStyleButton, hasNodeSelection); + gtk_widget_set_sensitive (removeStyleButton, hasStyleSelection); +} + +- (void) removeSelectedStyle { + NodeStyle *style = [selector selectedStyle]; + if (style) + [[selector styleManager] removeNodeStyle:style]; +} + +- (void) applySelectedStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + NodeStyle *style = [selector selectedStyle]; + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:style]; + } + + [document endModifyNodes]; +} + +- (void) clearSelectedStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:nil]; + } + + [document endModifyNodes]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NodeStyle *newStyle = [NodeStyle defaultNodeStyleWithName:@"newstyle"]; + [[palette styleManager] addNodeStyle:newStyle]; + [[palette styleManager] setActiveNodeStyle:newStyle]; + + [pool drain]; +} + +static void remove_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette removeSelectedStyle]; + [pool drain]; +} + +static void apply_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette applySelectedStyle]; + [pool drain]; +} + +static void clear_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette clearSelectedStyle]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:noet:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/PreambleEditor.h b/tikzit/src/linux/PreambleEditor.h new file mode 100644 index 0000000..6849d74 --- /dev/null +++ b/tikzit/src/linux/PreambleEditor.h @@ -0,0 +1,50 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> + +@class Preambles; + +@interface PreambleEditor: NSObject { + Preambles *preambles; + + // we don't keep any refs, as we control + // the top window + GtkWindow *parentWindow; + GtkWindow *window; + GtkListStore *preambleListStore; + GtkTreeView *preambleSelector; + GtkTextView *preambleView; + BOOL blockSignals; + BOOL adding; +} + +- (id) initWithPreambles:(Preambles*)p; + +- (void) setParentWindow:(GtkWindow*)parent; + +- (Preambles*) preambles; + +- (void) show; +- (void) hide; +- (BOOL) isVisible; +- (void) setVisible:(BOOL)visible; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PreambleEditor.m b/tikzit/src/linux/PreambleEditor.m new file mode 100644 index 0000000..eb05781 --- /dev/null +++ b/tikzit/src/linux/PreambleEditor.m @@ -0,0 +1,489 @@ +/* + * 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 "PreambleEditor.h" +#import "Preambles.h" + +enum { + NAME_COLUMN, + IS_CUSTOM_COLUMN, + N_COLUMNS +}; + +// {{{ Internal interfaces +// {{{ Signals +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor); +static void close_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void add_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void remove_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void undo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void redo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor); +static void preamble_name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PreambleEditor *editor); +static void preamble_selection_changed_cb (GtkTreeSelection *treeselection, + PreambleEditor *editor); +// }}} + +@interface PreambleEditor (Private) +- (void) loadUi; +- (void) save; +- (void) revert; +- (void) update; +- (void) fillListStore; +- (BOOL) isDefaultPreambleSelected; +- (NSString*) selectedPreambleName; +- (void) addPreamble; +- (void) deletePreamble; +- (void) renamePreambleAtPath:(gchar*)path to:(gchar*)newValue; +@end + +// }}} +// {{{ API + +@implementation PreambleEditor + +- (id) init { + [self release]; + self = nil; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p { + self = [super init]; + + if (self) { + preambles = [p retain]; + parentWindow = NULL; + window = NULL; + preambleView = NULL; + preambleSelector = NULL; + blockSignals = NO; + adding = NO; + } + + return self; +} + +- (Preambles*) preambles { + return preambles; +} + +- (void) setParentWindow:(GtkWindow*)parent { + parentWindow = parent; + if (window) { + gtk_window_set_transient_for (window, parentWindow); + } +} + +- (void) show { + [self loadUi]; + gtk_widget_show (GTK_WIDGET (window)); + [self revert]; +} + +- (void) hide { + if (!window) { + return; + } + [self save]; + gtk_widget_hide (GTK_WIDGET (window)); +} + +- (BOOL) isVisible { + if (!window) { + return NO; + } + gboolean visible; + g_object_get (G_OBJECT (window), "visible", &visible, NULL); + return visible ? YES : NO; +} + +- (void) setVisible:(BOOL)visible { + if (visible) { + [self show]; + } else { + [self hide]; + } +} + +- (void) dealloc { + [preambles release]; + preambles = nil; + gtk_widget_destroy (GTK_WIDGET (window)); + window = NULL; + + [super dealloc]; +} + +@end + +// }}} +// {{{ Private + +@implementation PreambleEditor (Private) +- (GtkWidget*) createPreambleList { + preambleListStore = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN); + preambleSelector = GTK_TREE_VIEW (gtk_tree_view_new_with_model ( + GTK_TREE_MODEL (preambleListStore))); + gtk_widget_set_size_request (GTK_WIDGET (preambleSelector), 150, -1); + gtk_tree_view_set_headers_visible (preambleSelector, FALSE); + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Preamble", + renderer, + "text", NAME_COLUMN, + "editable", IS_CUSTOM_COLUMN, + NULL); + gtk_tree_view_append_column (preambleSelector, column); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (preamble_name_edited_cb), + self); + + GtkWidget *listScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (listScroller), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (listScroller), + GTK_WIDGET (preambleSelector)); + + [self fillListStore]; + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); + g_signal_connect (G_OBJECT (sel), + "changed", + G_CALLBACK (preamble_selection_changed_cb), + self); + + return listScroller; +} + +- (void) loadUi { + if (window) { + return; + } + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title (window, "Preamble editor"); + gtk_window_set_modal (window, TRUE); + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + gtk_window_set_default_size (window, 600, 400); + gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DIALOG); + if (parentWindow) { + gtk_window_set_transient_for (window, parentWindow); + } + g_signal_connect (window, + "delete-event", + G_CALLBACK (window_delete_event_cb), + self); + + GtkWidget *mainBox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (window), mainBox); + + GtkPaned *paned = GTK_PANED (gtk_hpaned_new ()); + gtk_box_pack_start (GTK_BOX (mainBox), + GTK_WIDGET (paned), + TRUE, TRUE, 0); + + GtkWidget *listWidget = [self createPreambleList]; + GtkBox *listBox = GTK_BOX (gtk_vbox_new (FALSE, 0)); + gtk_box_pack_start (listBox, listWidget, TRUE, TRUE, 0); + + GtkContainer *listButtonBox = GTK_CONTAINER (gtk_hbox_new (FALSE, 0)); + gtk_box_pack_start (listBox, GTK_WIDGET (listButtonBox), FALSE, TRUE, 0); + GtkWidget *addButton = gtk_button_new_from_stock (GTK_STOCK_ADD); + g_signal_connect (addButton, + "clicked", + G_CALLBACK (add_button_clicked_cb), + self); + gtk_container_add (listButtonBox, addButton); + GtkWidget *removeButton = gtk_button_new_from_stock (GTK_STOCK_REMOVE); + g_signal_connect (removeButton, + "clicked", + G_CALLBACK (remove_button_clicked_cb), + self); + gtk_container_add (listButtonBox, removeButton); + + gtk_paned_pack1 (paned, GTK_WIDGET (listBox), FALSE, TRUE); + + preambleView = GTK_TEXT_VIEW (gtk_text_view_new ()); + GtkWidget *scroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller), + GTK_POLICY_AUTOMATIC, // horiz + GTK_POLICY_ALWAYS); // vert + gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (preambleView)); + gtk_paned_pack2 (paned, scroller, TRUE, TRUE); + + GtkContainer *buttonBox = GTK_CONTAINER (gtk_hbutton_box_new ()); + gtk_button_box_set_layout (GTK_BUTTON_BOX (buttonBox), GTK_BUTTONBOX_END); + gtk_box_pack_start (GTK_BOX (mainBox), + GTK_WIDGET (buttonBox), + FALSE, TRUE, 0); + GtkWidget *closeButton = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + gtk_container_add (buttonBox, closeButton); + g_signal_connect (closeButton, + "clicked", + G_CALLBACK (close_button_clicked_cb), + self); + /* + GtkWidget *undoButton = gtk_button_new_from_stock (GTK_STOCK_UNDO); + gtk_container_add (buttonBox, undoButton); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (buttonBox), + undoButton, + TRUE); + g_signal_connect (undoButton, + "clicked", + G_CALLBACK (undo_button_clicked_cb), + self); + GtkWidget *redoButton = gtk_button_new_from_stock (GTK_STOCK_REDO); + gtk_container_add (buttonBox, redoButton); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (buttonBox), + redoButton, + TRUE); + g_signal_connect (redoButton, + "clicked", + G_CALLBACK (redo_button_clicked_cb), + self); + */ + [self revert]; + + gtk_widget_show_all (mainBox); +} + +- (void) save { + if ([self isDefaultPreambleSelected]) + return; + GtkTextIter start,end; + GtkTextBuffer *preambleBuffer = gtk_text_view_get_buffer (preambleView); + gtk_text_buffer_get_bounds(preambleBuffer, &start, &end); + gchar *text = gtk_text_buffer_get_text(preambleBuffer, &start, &end, FALSE); + NSString *preamble = [NSString stringWithUTF8String:text]; + g_free (text); + [preambles setCurrentPreamble:preamble]; +} + +- (void) revert { + GtkTextBuffer *preambleBuffer = gtk_text_view_get_buffer (preambleView); + gtk_text_buffer_set_text (preambleBuffer, [[preambles currentPreamble] UTF8String], -1); + gtk_text_view_set_editable (preambleView, ![self isDefaultPreambleSelected]); +} + +- (void) update { + if (!blockSignals) { + [self save]; + } + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + GtkTreeIter row; + GtkTreeModel *model; + if (gtk_tree_selection_get_selected (sel, &model, &row)) { + gchar *name; + gtk_tree_model_get (model, &row, NAME_COLUMN, &name, -1); + NSString *preambleName = [NSString stringWithUTF8String:name]; + [preambles setSelectedPreambleName:preambleName]; + g_free (name); + } + [self revert]; +} + +- (void) fillListStore { + blockSignals = YES; + + GtkTreeIter row; + gtk_list_store_clear (preambleListStore); + + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [[preambles defaultPreambleName] UTF8String], + IS_CUSTOM_COLUMN, FALSE, + -1); + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + if ([self isDefaultPreambleSelected]) { + gtk_tree_selection_select_iter (sel, &row); + } + + NSEnumerator *en = [preambles customPreambleNameEnumerator]; + NSString *preambleName; + while ((preambleName = [en nextObject])) { + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [preambleName UTF8String], + IS_CUSTOM_COLUMN, TRUE, + -1); + if ([preambleName isEqualToString:[self selectedPreambleName]]) { + gtk_tree_selection_select_iter (sel, &row); + } + } + + blockSignals = NO; +} + +- (BOOL) isDefaultPreambleSelected { + return [preambles selectedPreambleIsDefault]; +} + +- (NSString*) selectedPreambleName { + return [preambles selectedPreambleName]; +} + +- (void) addPreamble { + NSString *newName = [preambles addPreamble]; + + GtkTreeIter row; + gtk_list_store_append (preambleListStore, &row); + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, [newName UTF8String], + IS_CUSTOM_COLUMN, TRUE, + -1); + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_select_iter (sel, &row); +} + +- (void) deletePreamble { + if ([self isDefaultPreambleSelected]) + return; + + NSString *name = [self selectedPreambleName]; + + GtkTreeIter row; + GtkTreeModel *model = GTK_TREE_MODEL (preambleListStore); + + gtk_tree_model_get_iter_first (model, &row); + // ignore first; it is the default one + gboolean found = FALSE; + while (!found && gtk_tree_model_iter_next (model, &row)) { + gchar *candidate; + gtk_tree_model_get (model, &row, NAME_COLUMN, &candidate, -1); + if (g_strcmp0 (candidate, [name UTF8String]) == 0) { + found = TRUE; + } + g_free (candidate); + } + + if (!found) + return; + + if (![preambles removePreamble:name]) + return; + + blockSignals = YES; + + gboolean had_next = gtk_list_store_remove (preambleListStore, &row); + if (!had_next) { + // select the last item + gint length = gtk_tree_model_iter_n_children (model, NULL); + gtk_tree_model_iter_nth_child (model, &row, NULL, length - 1); + } + + GtkTreeSelection *sel = gtk_tree_view_get_selection (preambleSelector); + gtk_tree_selection_select_iter (sel, &row); + + [self revert]; + + blockSignals = NO; +} + +- (void) renamePreambleAtPath:(gchar*)path to:(gchar*)newValue { + NSString *newName = [NSString stringWithUTF8String:newValue]; + + GtkTreeIter row; + GtkTreeModel *model = GTK_TREE_MODEL (preambleListStore); + + if (!gtk_tree_model_get_iter_from_string (model, &row, path)) + return; + + gchar *oldValue; + gtk_tree_model_get (model, &row, NAME_COLUMN, &oldValue, -1); + + NSString* oldName = [NSString stringWithUTF8String:oldValue]; + if ([preambles renamePreambleFrom:oldName to:newName]) { + gtk_list_store_set (preambleListStore, &row, + NAME_COLUMN, newValue, + -1); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor hide]; + [pool drain]; + return TRUE; // we dealt with this event +} + +static void close_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor hide]; + [pool drain]; +} + +static void add_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addPreamble]; + [pool drain]; +} + +static void remove_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor deletePreamble]; + [pool drain]; +} + +static void undo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog(@"Undo"); + [pool drain]; +} + +static void redo_button_clicked_cb (GtkButton *widget, PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSLog(@"Redo"); + [pool drain]; +} + +static void preamble_name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor renamePreambleAtPath:path to:new_text]; + [pool drain]; +} + +static void preamble_selection_changed_cb (GtkTreeSelection *treeselection, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor update]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:noet:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/Preambles+Storage.h b/tikzit/src/linux/Preambles+Storage.h new file mode 100644 index 0000000..76f56cc --- /dev/null +++ b/tikzit/src/linux/Preambles+Storage.h @@ -0,0 +1,29 @@ +/* + * 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 "Preambles.h" + +@interface Preambles (Storage) + ++ (Preambles*) preamblesFromDirectory:(NSString*)directory; +- (id) initFromDirectory:(NSString*)directory; +- (void) loadFromDirectory:(NSString*)directory; +- (void) storeToDirectory:(NSString*)directory; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Preambles+Storage.m b/tikzit/src/linux/Preambles+Storage.m new file mode 100644 index 0000000..bd3ea03 --- /dev/null +++ b/tikzit/src/linux/Preambles+Storage.m @@ -0,0 +1,84 @@ +/* + * 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 "Preambles+Storage.h" + +static NSString *ext = @"preamble"; + +@implementation Preambles (Storage) + ++ (Preambles*) preamblesFromDirectory:(NSString*)directory { + return [[[self alloc] initFromDirectory:directory] autorelease]; +} + +- (id) initFromDirectory:(NSString*)directory { + BOOL isDir = NO; + if ([[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDir] && isDir) { + self = [super init]; + + if (self) { + selectedPreambleName = @"default"; + preambleDict = nil; + [self loadFromDirectory:directory]; + } + } else { + self = [self init]; + } + + return self; +} + +- (void) loadFromDirectory:(NSString*)directory { + preambleDict = [[NSMutableDictionary alloc] initWithCapacity:1]; + NSDirectoryEnumerator *en = [[NSFileManager defaultManager] enumeratorAtPath:directory]; + NSString *filename; + while ((filename = [en nextObject]) != nil) { + if ([filename hasSuffix:ext] && [[en fileAttributes] fileType] == NSFileTypeRegular) { + NSString *path = [directory stringByAppendingPathComponent:filename]; + NSString *contents = [NSString stringWithContentsOfFile:path]; + if (contents) { + [preambleDict setObject:contents forKey:[filename stringByDeletingPathExtension]]; + } + } + } +} + +- (void) storeToDirectory:(NSString*)directory { + NSDirectoryEnumerator *den = [[NSFileManager defaultManager] enumeratorAtPath:directory]; + NSString *filename; + while ((filename = [den nextObject]) != nil) { + if ([filename hasSuffix:ext] && [[den fileAttributes] fileType] == NSFileTypeRegular) { + NSString *path = [directory stringByAppendingPathComponent:filename]; + NSString *entry = [filename stringByDeletingPathExtension]; + if ([preambleDict objectForKey:entry] == nil) { + [[NSFileManager defaultManager] removeFileAtPath:path handler:nil]; + } + } + } + + NSEnumerator *en = [self customPreambleNameEnumerator]; + NSString *entry; + while ((entry = [en nextObject]) != nil) { + NSString *path = [directory stringByAppendingPathComponent:[entry stringByAppendingPathExtension:ext]]; + NSString *contents = [preambleDict objectForKey:entry]; + [contents writeToFile:path atomically:YES]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PreviewRenderer.h b/tikzit/src/linux/PreviewRenderer.h new file mode 100644 index 0000000..da617b3 --- /dev/null +++ b/tikzit/src/linux/PreviewRenderer.h @@ -0,0 +1,47 @@ +/* + * 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 "TZFoundation.h" +#import <poppler.h> +#import "Preambles.h" +#import "Surface.h" +#import "TikzDocument.h" + +@interface PreviewRenderer: NSObject<RenderDelegate> { + Preambles *preambles; + TikzDocument *document; + PopplerDocument *pdfDocument; + PopplerPage *pdfPage; +} + +- (id) initWithPreambles:(Preambles*)p; + +- (BOOL) updateWithError:(NSError**)error; +- (BOOL) update; +- (BOOL) isValid; + +- (Preambles*) preambles; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)doc; + +- (double) width; +- (double) height; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PreviewRenderer.m b/tikzit/src/linux/PreviewRenderer.m new file mode 100644 index 0000000..5333d65 --- /dev/null +++ b/tikzit/src/linux/PreviewRenderer.m @@ -0,0 +1,207 @@ +/* + * 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 "PreviewRenderer.h" +#import "CairoRenderContext.h" + +@implementation PreviewRenderer + +- (id) init { + [self release]; + self = nil; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p { + self = [super init]; + + if (self) { + document = nil; + preambles = [p retain]; + pdfDocument = NULL; + pdfPage = NULL; + } + + return self; +} + +- (BOOL) update { + NSError *error = nil; + BOOL result = [self updateWithError:&error]; + if (error) { + logError (error, @"Could not update preview"); + if ([error code] == TZ_ERR_TOOL_FAILED) { + NSLog (@"Output: %@", [[error userInfo] objectForKey:TZToolOutputErrorKey]); + } + } + return result; +} + +- (BOOL) updateWithError:(NSError**)error { + if (document == nil) { + if (error) { + *error = [NSError errorWithMessage:@"No document given" code:TZ_ERR_BADSTATE]; + } + if (pdfDocument) { + g_object_unref (pdfDocument); + pdfDocument = NULL; + } + if (pdfPage) { + g_object_unref (pdfPage); + pdfPage = NULL; + } + return NO; + } + + NSString *tex = [NSString stringWithFormat:@"%@%@%@", + [preambles currentPreamble], + [document tikz], + [preambles currentPostamble]]; + + NSString *tempDir = [[NSFileManager defaultManager] createTempDirectoryWithError:error]; + if (!tempDir) { + if (error) { + *error = [NSError errorWithMessage:@"Could not create temporary directory" code:TZ_ERR_IO cause:*error]; + } + return NO; + } + + // write tex code to temporary file + NSString *texFile = [NSString stringWithFormat:@"%@/tikzit.tex", tempDir]; + NSString *pdfFile = [NSString stringWithFormat:@"file://%@/tikzit.pdf", tempDir]; + [tex writeToFile:texFile atomically:YES]; + + // run pdflatex in a bash shell + NSTask *latexTask = [[NSTask alloc] init]; + [latexTask setCurrentDirectoryPath:tempDir]; + [latexTask setLaunchPath:@"/bin/bash"]; + + // This assumes the user has $PATH set up to find pdflatex + // This should be improved to take other path setups into account + // and to be customisable. + NSString *latexCmd = [NSString stringWithFormat: + @"pdflatex -interaction=nonstopmode -halt-on-error '%@'\n", + texFile]; + + NSPipe *pout = [NSPipe pipe]; + NSPipe *pin = [NSPipe pipe]; + [latexTask setStandardOutput:pout]; + [latexTask setStandardInput:pin]; + + NSFileHandle *latexIn = [pin fileHandleForWriting]; + NSFileHandle *latexOut = [pout fileHandleForReading]; + + [latexTask launch]; + [latexIn writeData:[latexCmd dataUsingEncoding:NSUTF8StringEncoding]]; + [latexIn closeFile]; + [latexTask waitUntilExit]; + + NSData *data = [latexOut readDataToEndOfFile]; + NSString *str = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + BOOL success = NO; + + if ([latexTask terminationStatus] != 0) { + if (error) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:@"Generating a PDF file with pdflatex failed" forKey:NSLocalizedDescriptionKey]; + [errorDetail setValue:str forKey:TZToolOutputErrorKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_TOOL_FAILED userInfo:errorDetail]; + } + } else { + // load pdf document + GError* gerror = NULL; + pdfDocument = poppler_document_new_from_file([pdfFile UTF8String], NULL, &gerror); + if (!pdfDocument) { + if (error) { + *error = [NSError errorWithMessage:[NSString stringWithFormat:@"Could not load PDF document", pdfFile] + code:TZ_ERR_IO + cause:[NSError errorWithGError:gerror]]; + } + g_error_free(gerror); + } else { + pdfPage = poppler_document_get_page(pdfDocument, 0); + if(!pdfPage) { + if (error) { + *error = [NSError errorWithMessage:@"Could not open first page of PDF document" + code:TZ_ERR_OTHER]; + } + g_object_unref(pdfDocument); + } else { + success = YES; + } + } + } + + // remove all temporary files + [[NSFileManager defaultManager] removeFileAtPath:tempDir handler:NULL]; + + return success; +} + +- (BOOL) isValid { + return pdfPage ? YES : NO; +} + +- (Preambles*) preambles { + return preambles; +} + +- (TikzDocument*) document { + return document; +} + +- (void) setDocument:(TikzDocument*)doc { + [doc retain]; + [document release]; + document = doc; +} + +- (double) width { + double w = 0.0; + if (pdfPage) + poppler_page_get_size(pdfPage, &w, NULL); + return w; +} + +- (double) height { + double h = 0.0; + if (pdfPage) + poppler_page_get_size(pdfPage, NULL, &h); + return h; +} + +- (void) renderWithContext:(id<RenderContext>)c onSurface:(id<Surface>)surface { + if (document != nil) { + CairoRenderContext *context = (CairoRenderContext*)c; + + [context saveState]; + [context applyTransform:[surface transformer]]; + + // white-out + [context paintWithColor:WhiteRColor]; + + poppler_page_render (pdfPage, [context cairoContext]); + + [context restoreState]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PreviewWindow.h b/tikzit/src/linux/PreviewWindow.h new file mode 100644 index 0000000..d59954e --- /dev/null +++ b/tikzit/src/linux/PreviewWindow.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "TZFoundation.h" +#import <gtk/gtk.h> + +@class Preambles; +@class PreviewRenderer; +@class TikzDocument; +@class WidgetSurface; + +@interface PreviewWindow: NSObject { + PreviewRenderer *previewer; + GtkWindow *window; + WidgetSurface *surface; + GtkWindow *parent; +} + +- (id) initWithPreambles:(Preambles*)p; + +- (void) setParentWindow:(GtkWindow*)parent; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)doc; + +- (BOOL) update; + +- (void) show; +- (void) hide; +- (BOOL) isVisible; +- (void) setVisible:(BOOL)visible; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PreviewWindow.m b/tikzit/src/linux/PreviewWindow.m new file mode 100644 index 0000000..7bbcf18 --- /dev/null +++ b/tikzit/src/linux/PreviewWindow.m @@ -0,0 +1,191 @@ +/* + * 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 "PreviewWindow.h" + +#import "Preambles.h" +#import "PreviewRenderer.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + +#import "gtkhelpers.h" + +@interface PreviewWindow (Private) +- (BOOL) updateOrShowError; +@end + +// {{{ API +@implementation PreviewWindow + +- (id) init { + [self release]; + self = nil; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p { + self = [super init]; + + if (self) { + parent = NULL; + previewer = [[PreviewRenderer alloc] initWithPreambles:p]; + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title (window, "Preview"); + gtk_window_set_resizable (window, FALSE); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), + NULL); + + GtkWidget *pdfArea = gtk_drawing_area_new (); + gtk_container_add (GTK_CONTAINER (window), pdfArea); + gtk_widget_set_size_request (pdfArea, 150.0, 150.0); + gtk_widget_show (pdfArea); + surface = [[WidgetSurface alloc] initWithWidget:pdfArea]; + [surface setRenderDelegate:previewer]; + Transformer *t = [surface transformer]; + [t setFlippedAboutXAxis:![t isFlippedAboutXAxis]]; + } + + return self; +} + +- (void) setParentWindow:(GtkWindow*)p { + parent = p; + gtk_window_set_transient_for (window, p); + if (p != NULL) { + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + } +} + +- (TikzDocument*) document { + return [previewer document]; +} + +- (void) setDocument:(TikzDocument*)doc { + [previewer setDocument:doc]; +} + +- (void) updateSize { + double width = 150; + double height = 150; + if ([previewer isValid]) { + double pWidth = [previewer width]; + double pHeight = [previewer height]; + width = (width < pWidth + 4) ? pWidth + 4 : width; + height = (height < pHeight + 4) ? pHeight + 4 : height; + NSPoint offset; + offset.x = (width-pWidth)/2.0; + offset.y = (height-pHeight)/2.0; + [[surface transformer] setOrigin:offset]; + } + [surface setSizeRequestWidth:width height:height]; +} + +- (BOOL) update { + if ([self updateOrShowError]) { + [self updateSize]; + [surface invalidate]; + return YES; + } + + return NO; +} + +- (void) show { + if ([self updateOrShowError]) { + [self updateSize]; + gtk_widget_show (GTK_WIDGET (window)); + [surface invalidate]; + } +} + +- (void) hide { + gtk_widget_hide (GTK_WIDGET (window)); +} + +- (BOOL) isVisible { + gboolean visible; + g_object_get (G_OBJECT (window), "visible", &visible, NULL); + return visible ? YES : NO; +} + +- (void) setVisible:(BOOL)visible { + if (visible) { + [self show]; + } else { + [self hide]; + } +} + +- (void) dealloc { + [previewer release]; + [surface release]; + gtk_widget_destroy (GTK_WIDGET (window)); + + [super dealloc]; +} + +@end +// }}} + +@implementation PreviewWindow (Private) +- (BOOL) updateOrShowError { + NSError *error = nil; + if (![previewer updateWithError:&error]) { + GtkWindow *dparent = gtk_widget_get_visible (GTK_WIDGET (window)) + ? window + : parent; + GtkWidget *dialog = gtk_message_dialog_new (dparent, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Failed to generate the preview: %s", + [[error localizedDescription] UTF8String]); +#if GTK_CHECK_VERSION(2,22,0) + if ([error code] == TZ_ERR_TOOL_FAILED) { + GtkBox *box = GTK_BOX (gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog))); + GtkWidget *label = gtk_label_new ("pdflatex said:"); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5f); + gtk_widget_show (label); + gtk_box_pack_start (box, label, FALSE, TRUE, 0); + + GtkWidget *view = gtk_text_view_new (); + GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gtk_text_buffer_set_text (buffer, [[error toolOutput] UTF8String], -1); + gtk_text_view_set_editable (GTK_TEXT_VIEW (view), FALSE); + gtk_widget_show (view); + GtkWidget *scrolledView = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledView), + GTK_POLICY_NEVER, // horiz + GTK_POLICY_ALWAYS); // vert + gtk_widget_set_size_request (scrolledView, -1, 120); + gtk_container_add (GTK_CONTAINER (scrolledView), view); + gtk_widget_show (scrolledView); + gtk_box_pack_start (box, scrolledView, TRUE, TRUE, 0); + } +#endif // GTK+ 2.22.0 + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + return NO; + } + return YES; +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PropertyListEditor.h b/tikzit/src/linux/PropertyListEditor.h new file mode 100644 index 0000000..fd7e395 --- /dev/null +++ b/tikzit/src/linux/PropertyListEditor.h @@ -0,0 +1,63 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "GraphElementData.h" +#import "GraphElementProperty.h" + +@protocol PropertyChangeDelegate +@optional +- (BOOL)startEdit; +- (void)endEdit; +- (void)cancelEdit; +@end + +@interface PropertyListEditor: NSObject { + GtkListStore *list; + GtkWidget *view; + GraphElementData *data; + GtkWidget *widget; + NSObject<PropertyChangeDelegate> *delegate; +} + +/*! + @property widget + @brief The widget displaying the editable list + */ +@property (readonly) GtkWidget *widget; + +/*! + @property data + @brief The GraphElementData that should be reflected in the list + */ +@property (retain) GraphElementData *data; + +/*! + @property delegate + @brief A delegate for dealing with property changes + */ +@property (retain) NSObject<PropertyChangeDelegate> *delegate; + +/*! + * Reload the properties from the data store + */ +- (void) reloadProperties; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PropertyListEditor.m b/tikzit/src/linux/PropertyListEditor.m new file mode 100644 index 0000000..cf04726 --- /dev/null +++ b/tikzit/src/linux/PropertyListEditor.m @@ -0,0 +1,417 @@ +/* + * 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 "PropertyListEditor.h" + +// {{{ Constants + +enum { + PLM_NAME_COL = 0, + PLM_VALUE_COL, + PLM_IS_PROPERTY_COL, + PLM_PROPERTY_COL, + PLM_N_COLS +}; + +// }}} +// {{{ Internal interfaces +// {{{ Signals + +static void value_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor); +static void name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor); +static void add_prop_clicked_cb (GtkButton *button, + PropertyListEditor *editor); +static void add_atom_clicked_cb (GtkButton *button, + PropertyListEditor *editor); +static void remove_clicked_cb (GtkButton *button, + PropertyListEditor *editor); + +// }}} +// {{{ Private + +@interface PropertyListEditor (Private) +- (void) updatePath:(gchar*)path withValue:(NSString*)newText; +- (void) updatePath:(gchar*)path withName:(NSString*)newText; +- (void) addProperty; +- (void) addAtom; +- (void) removeSelected; +@end + +// }}} +// }}} +// {{{ API + +@implementation PropertyListEditor + +- (id) init { + self = [super init]; + + if (self) { + list = gtk_list_store_new (PLM_N_COLS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_BOOLEAN, + G_TYPE_POINTER); + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list)); + GtkWidget *scrolledview = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledview), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (scrolledview), view); + gtk_widget_set_size_request (view, -1, 150); + data = nil; + delegate = nil; + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "editable", TRUE, + NULL); + column = gtk_tree_view_column_new_with_attributes ("Key/Atom", + renderer, + "text", PLM_NAME_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (name_edited_cb), + self); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Value", + renderer, + "text", PLM_VALUE_COL, + "editable", PLM_IS_PROPERTY_COL, + "sensitive", PLM_IS_PROPERTY_COL, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); + g_signal_connect (G_OBJECT (renderer), + "edited", + G_CALLBACK (value_edited_cb), + self); + + widget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (G_OBJECT (widget)); + gtk_container_add (GTK_CONTAINER (widget), scrolledview); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (widget), GTK_WIDGET (buttonBox), FALSE, FALSE, 0); + + GtkWidget *addPropButton = gtk_button_new (); + //gtk_widget_set_size_request (addPropButton, 27, 27); + gtk_widget_set_tooltip_text (addPropButton, "Add property"); + GtkWidget *addPropContents = gtk_hbox_new(FALSE, 0); + GtkWidget *addPropIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addPropContents), addPropIcon); + gtk_container_add (GTK_CONTAINER (addPropContents), gtk_label_new ("P")); + gtk_container_add (GTK_CONTAINER (addPropButton), addPropContents); + gtk_box_pack_start (buttonBox, addPropButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addPropButton), + "clicked", + G_CALLBACK (add_prop_clicked_cb), + self); + + GtkWidget *addAtomButton = gtk_button_new (); + //gtk_widget_set_size_request (addAtomButton, 27, 27); + gtk_widget_set_tooltip_text (addAtomButton, "Add atom"); + GtkWidget *addAtomContents = gtk_hbox_new(FALSE, 0); + GtkWidget *addAtomIcon = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (addAtomContents), addAtomIcon); + gtk_container_add (GTK_CONTAINER (addAtomContents), gtk_label_new ("A")); + gtk_container_add (GTK_CONTAINER (addAtomButton), addAtomContents); + gtk_box_pack_start (buttonBox, addAtomButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (addAtomButton), + "clicked", + G_CALLBACK (add_atom_clicked_cb), + self); + + GtkWidget *removeButton = gtk_button_new (); + //gtk_widget_set_size_request (removeButton, 27, 27); + gtk_widget_set_tooltip_text (removeButton, "Remove selected"); + GtkWidget *removeIcon = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add (GTK_CONTAINER (removeButton), removeIcon); + gtk_box_pack_start (buttonBox, removeButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeButton), + "clicked", + G_CALLBACK (remove_clicked_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + gtk_widget_show_all (scrolledview); + } + + return self; +} + +- (void) clearStore { + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list), &iter)) { + do { + void *prop; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &prop, + -1); + [(id)prop release]; + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &iter)); + gtk_list_store_clear (list); + } +} + +- (void) reloadProperties { + [self clearStore]; + int pos = 0; + for (GraphElementProperty *p in data) { + GtkTreeIter iter; + [p retain]; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, [[p key] UTF8String], + PLM_VALUE_COL, [[p value] UTF8String], + PLM_IS_PROPERTY_COL, ![p isAtom], + PLM_PROPERTY_COL, (void *)p, + -1); + ++pos; + } +} + +- (GtkWidget*) widget { return widget; } +- (GraphElementData*) data { return data; } +- (void) setData:(GraphElementData*)d { + [d retain]; + [data release]; + data = d; + [self reloadProperties]; +} + +- (NSObject<PropertyChangeDelegate>*) delegate { + return delegate; +} + +- (void) setDelegate:(NSObject<PropertyChangeDelegate>*)d { + id oldDelegate = delegate; + delegate = [d retain]; + [oldDelegate release]; +} + +- (void) dealloc { + [self clearStore]; + [data release]; + g_object_unref (list); + g_object_unref (widget); + [super dealloc]; +} + +@end + +// }}} +// {{{ Private + +@implementation PropertyListEditor (Private) +- (void) updatePath:(gchar*)pathStr withValue:(NSString*)newText { + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string (pathStr); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path)) { + gtk_tree_path_free (path); + return; + } + + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + + if (![prop isAtom]) { + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [prop setValue:newText]; + gtk_list_store_set (list, &iter, + PLM_VALUE_COL, [newText UTF8String], + -1); + [delegate endEdit]; + } + } + + gtk_tree_path_free (path); +} + +- (void) updatePath:(gchar*)pathStr withName:(NSString*)newText { + GtkTreeIter iter; + GtkTreePath *path = gtk_tree_path_new_from_string (pathStr); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path)) { + gtk_tree_path_free (path); + return; + } + + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), &iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + if ([newText isEqualToString:@""]) { + [data removeObjectIdenticalTo:prop]; + gtk_list_store_remove (list, &iter); + [prop release]; + } else { + [prop setKey:newText]; + gtk_list_store_set (list, &iter, + PLM_NAME_COL, [newText UTF8String], + -1); + } + [delegate endEdit]; + } + + gtk_tree_path_free (path); +} + +- (void) addProperty { + GtkTreeIter iter; + GraphElementProperty *p = [[GraphElementProperty alloc] initWithPropertyValue:@"" forKey:@"new property"]; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data addObject:p]; + gint pos = [data count] - 1; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, "new property", + PLM_VALUE_COL, "", + PLM_IS_PROPERTY_COL, TRUE, + PLM_PROPERTY_COL, (void *)p, + -1); + [delegate endEdit]; + } else { + [p release]; + } +} + +- (void) addAtom { + GtkTreeIter iter; + GraphElementProperty *p = [[GraphElementProperty alloc] initWithAtomName:@"new atom"]; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data addObject:p]; + gint pos = [data count] - 1; + gtk_list_store_insert_with_values (list, &iter, pos, + PLM_NAME_COL, "new atom", + PLM_VALUE_COL, [[p value] UTF8String], + PLM_IS_PROPERTY_COL, FALSE, + PLM_PROPERTY_COL, (void *)p, + -1); + [delegate endEdit]; + } else { + [p release]; + } +} + +- (void) removeSelected { + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + GList *selPaths = gtk_tree_selection_get_selected_rows (selection, NULL); + GList *selIters = NULL; + + // Convert to iters, as GtkListStore has persistent iters + GList *curr = selPaths; + while (curr != NULL) { + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (list), + &iter, + (GtkTreePath*)curr->data); + selIters = g_list_prepend (selIters, gtk_tree_iter_copy (&iter)); + curr = g_list_next (curr); + } + + // remove all iters + curr = selIters; + while (curr != NULL) { + GtkTreeIter *iter = (GtkTreeIter*)curr->data; + void *propPtr; + gtk_tree_model_get (GTK_TREE_MODEL (list), iter, + PLM_PROPERTY_COL, &propPtr, + -1); + GraphElementProperty *prop = (GraphElementProperty*)propPtr; + if (![delegate respondsToSelector:@selector(startEdit)] || [delegate startEdit]) { + [data removeObjectIdenticalTo:prop]; + gtk_list_store_remove (list, iter); + [prop release]; + [delegate endEdit]; + } + curr = g_list_next (curr); + } + + g_list_foreach (selIters, (GFunc) gtk_tree_iter_free, NULL); + g_list_free (selIters); + g_list_foreach (selPaths, (GFunc) gtk_tree_path_free, NULL); + g_list_free (selPaths); +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void value_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor updatePath:path withValue:[NSString stringWithUTF8String:new_text]]; + [pool drain]; +} + +static void name_edited_cb (GtkCellRendererText *renderer, + gchar *path, + gchar *new_text, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor updatePath:path withName:[NSString stringWithUTF8String:new_text]]; + [pool drain]; +} + +static void add_prop_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addProperty]; + [pool drain]; +} + +static void add_atom_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor addAtom]; + [pool drain]; +} + +static void remove_clicked_cb (GtkButton *button, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor removeSelected]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/PropertyPane.h b/tikzit/src/linux/PropertyPane.h new file mode 100644 index 0000000..5b6ef1b --- /dev/null +++ b/tikzit/src/linux/PropertyPane.h @@ -0,0 +1,67 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "Configuration.h" +#import "TikzDocument.h" +#import "PropertyListEditor.h" + +@class GraphPropertyDelegate; +@class NodePropertyDelegate; +@class EdgePropertyDelegate; +@class EdgeNodePropertyDelegate; + +@interface PropertyPane: NSObject { + TikzDocument *document; + BOOL blockUpdates; + + PropertyListEditor *graphProps; + PropertyListEditor *nodeProps; + PropertyListEditor *edgeProps; + PropertyListEditor *edgeNodeProps; + + GraphPropertyDelegate *graphPropDelegate; + NodePropertyDelegate *nodePropDelegate; + EdgePropertyDelegate *edgePropDelegate; + EdgeNodePropertyDelegate *edgeNodePropDelegate; + + GtkWidget *propertiesPane; + + GtkExpander *graphPropsExpander; + GtkExpander *nodePropsExpander; + GtkExpander *edgePropsExpander; + + GtkEntry *nodeLabelEntry; + GtkToggleButton *edgeNodeToggle; + GtkWidget *edgeNodePropsWidget; + GtkEntry *edgeNodeLabelEntry; +} + +@property (readonly) GtkWidget *widget; + +- (id) init; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)doc; + +- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group; +- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/PropertyPane.m b/tikzit/src/linux/PropertyPane.m new file mode 100644 index 0000000..ff3b9a4 --- /dev/null +++ b/tikzit/src/linux/PropertyPane.m @@ -0,0 +1,572 @@ +/* + * 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 "PropertyPane.h" +#import "GraphElementProperty.h" +#import "gtkhelpers.h" + +// {{{ Internal interfaces +// {{{ GTK+ helpers +static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry); +static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry); +// }}} +// {{{ GTK+ callbacks +static void node_label_changed_cb (GtkEditable *widget, PropertyPane *pane); +static void edge_node_label_changed_cb (GtkEditable *widget, PropertyPane *pane); +static void edge_node_toggled_cb (GtkToggleButton *widget, PropertyPane *pane); +// }}} + +@interface PropertyPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n; +- (void) edgeSelectionChanged:(NSNotification*)n; +- (void) graphChanged:(NSNotification*)n; +- (void) nodeLabelEdited:(NSString*)newValue; +- (void) edgeNodeLabelEdited:(NSString*)newValue; +- (void) edgeNodeToggled:(BOOL)newValue; +@end + +@interface PropertyPane (Private) +- (void) updateGraphPane; +- (void) updateNodePane; +- (void) updateEdgePane; +- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents; +@end + +// {{{ Delegates + +@interface GraphPropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; +} +- (void) setDocument:(TikzDocument*)d; +@end + +@interface NodePropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; + Node *node; +} +- (void) setDocument:(TikzDocument*)d; +- (void) setNode:(Node*)n; +@end + +@interface EdgePropertyDelegate : NSObject<PropertyChangeDelegate> { + TikzDocument *doc; + Edge *edge; +} +- (void) setDocument:(TikzDocument*)d; +- (void) setEdge:(Edge*)e; +@end + +// }}} + +// }}} +// {{{ API + +@implementation PropertyPane + +@synthesize widget=propertiesPane; + +- (id) init { + self = [super init]; + + if (self) { + document = nil; + blockUpdates = NO; + + graphProps = [[PropertyListEditor alloc] init]; + graphPropDelegate = [[GraphPropertyDelegate alloc] init]; + [graphProps setDelegate:graphPropDelegate]; + + nodeProps = [[PropertyListEditor alloc] init]; + nodePropDelegate = [[NodePropertyDelegate alloc] init]; + [nodeProps setDelegate:nodePropDelegate]; + + edgeProps = [[PropertyListEditor alloc] init]; + edgePropDelegate = [[EdgePropertyDelegate alloc] init]; + [edgeProps setDelegate:edgePropDelegate]; + + edgeNodeProps = [[PropertyListEditor alloc] init]; + [edgeNodeProps setDelegate:edgePropDelegate]; + + propertiesPane = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (propertiesPane); + + /* + * Graph properties + */ + graphPropsExpander = [self _addExpanderWithName:"Graph properties" + contents:[graphProps widget]]; + g_object_ref_sink (graphPropsExpander); + + + /* + * Node properties + */ + GtkWidget *nodePropsWidget = createPropsPaneWithLabelEntry(nodeProps, &nodeLabelEntry); + g_object_ref (nodeLabelEntry); + nodePropsExpander = [self _addExpanderWithName:"Node properties" + contents:nodePropsWidget]; + g_object_ref (nodePropsExpander); + g_signal_connect (G_OBJECT (nodeLabelEntry), + "changed", + G_CALLBACK (node_label_changed_cb), + self); + + + /* + * Edge properties + */ + GtkBox *edgePropsBox = GTK_BOX (gtk_vbox_new (FALSE, 0)); + edgePropsExpander = [self _addExpanderWithName:"Edge properties" + contents:GTK_WIDGET (edgePropsBox)]; + g_object_ref (edgePropsExpander); + + gtk_widget_show ([edgeProps widget]); + gtk_box_pack_start (edgePropsBox, [edgeProps widget], FALSE, TRUE, 0); + + edgeNodeToggle = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_label ("Child node")); + g_object_ref (edgeNodeToggle); + gtk_widget_show (GTK_WIDGET (edgeNodeToggle)); + gtk_box_pack_start (edgePropsBox, GTK_WIDGET (edgeNodeToggle), FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (GTK_WIDGET (edgeNodeToggle)), + "toggled", + G_CALLBACK (edge_node_toggled_cb), + self); + + edgeNodePropsWidget = createPropsPaneWithLabelEntry(edgeNodeProps, &edgeNodeLabelEntry); + g_object_ref (edgeNodePropsWidget); + g_object_ref (edgeNodeLabelEntry); + gtk_widget_show (edgeNodePropsWidget); + gtk_box_pack_start (edgePropsBox, edgeNodePropsWidget, FALSE, TRUE, 0); + g_signal_connect (G_OBJECT (edgeNodeLabelEntry), + "changed", + G_CALLBACK (edge_node_label_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [document release]; + + g_object_unref (propertiesPane); + g_object_unref (graphPropsExpander); + g_object_unref (nodePropsExpander); + g_object_unref (edgePropsExpander); + g_object_unref (nodeLabelEntry); + g_object_unref (edgeNodeToggle); + g_object_unref (edgeNodePropsWidget); + g_object_unref (edgeNodeLabelEntry); + + [graphProps release]; + [nodeProps release]; + [edgeProps release]; + [edgeNodeProps release]; + [graphPropDelegate release]; + [nodePropDelegate release]; + [edgePropDelegate release]; + + [super dealloc]; +} + +- (TikzDocument*) document { + return document; +} + +- (void) setDocument:(TikzDocument*)doc { + if (document != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + } + + [graphPropDelegate setDocument:doc]; + [nodePropDelegate setDocument:doc]; + [edgePropDelegate setDocument:doc]; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeSelectionChanged:) + name:@"NodeSelectionChanged" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeSelectionChanged:) + name:@"EdgeSelectionChanged" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(graphChanged:) + name:@"TikzChanged" object:doc]; + } + + [self updateGraphPane]; + [self updateNodePane]; + [self updateEdgePane]; + + [doc retain]; + [document release]; + document = doc; +} + +- (void) restoreUiStateFromConfig:(Configuration*)file group:(NSString*)group { + gtk_expander_set_expanded (graphPropsExpander, + [file booleanEntry:@"graph-props-expanded" + inGroup:group + withDefault:NO]); + gtk_expander_set_expanded (nodePropsExpander, + [file booleanEntry:@"node-props-expanded" + inGroup:group + withDefault:YES]); + gtk_expander_set_expanded (edgePropsExpander, + [file booleanEntry:@"edge-props-expanded" + inGroup:group + withDefault:NO]); +} + +- (void) saveUiStateToConfig:(Configuration*)file group:(NSString*)group { + [file setBooleanEntry:@"graph-props-expanded" + inGroup:group + value:gtk_expander_get_expanded (graphPropsExpander)]; + [file setBooleanEntry:@"node-props-expanded" + inGroup:group + value:gtk_expander_get_expanded (nodePropsExpander)]; + [file setBooleanEntry:@"edge-props-expanded" + inGroup:group + value:gtk_expander_get_expanded (edgePropsExpander)]; +} + +@end +// }}} +// {{{ Notifications + +@implementation PropertyPane (Notifications) + +- (void) nodeSelectionChanged:(NSNotification*)n { + [self updateNodePane]; +} + +- (void) edgeSelectionChanged:(NSNotification*)n { + [self updateEdgePane]; +} + +- (void) graphChanged:(NSNotification*)n { + [self updateGraphPane]; + [self updateNodePane]; + [self updateEdgePane]; +} + +- (void) nodeLabelEdited:(NSString*)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedNodes]; + if ([sel count] != 1) { + NSLog(@"Expected single node selected; got %lu", [sel count]); + return; + } + + Node *node = [sel anyObject]; + [document startModifyNode:node]; + [node setLabel:newValue]; + [document endModifyNode]; +} + +- (void) edgeNodeLabelEdited:(NSString*)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return; + } + + Edge *edge = [sel anyObject]; + if (![edge hasEdgeNode]) { + NSLog(@"Expected edge with edge node"); + return; + } + + [document startModifyEdge:edge]; + [[edge edgeNode] setLabel:newValue]; + [document endModifyEdge]; +} + +- (void) edgeNodeToggled:(BOOL)newValue { + if (blockUpdates) + return; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return; + } + + Edge *edge = [sel anyObject]; + + [document startModifyEdge:edge]; + [edge setHasEdgeNode:newValue]; + [document endModifyEdge]; +} + +@end +// }}} +// {{{ Private + +@implementation PropertyPane (Private) + +- (void) updateGraphPane { + blockUpdates = YES; + + GraphElementData *data = [[document graph] data]; + [graphProps setData:data]; + gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (graphPropsExpander)), data != nil); + + blockUpdates = NO; +} + +- (void) updateNodePane { + blockUpdates = YES; + + NSSet *sel = [[document pickSupport] selectedNodes]; + if ([sel count] == 1) { + Node *n = [sel anyObject]; + [nodePropDelegate setNode:n]; + [nodeProps setData:[n data]]; + gtk_entry_set_text (nodeLabelEntry, [[n label] UTF8String]); + gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (nodePropsExpander)), TRUE); + } else { + [nodePropDelegate setNode:nil]; + [nodeProps setData:nil]; + gtk_entry_set_text (nodeLabelEntry, ""); + gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (nodePropsExpander)), FALSE); + } + + blockUpdates = NO; +} + +- (void) updateEdgePane { + blockUpdates = YES; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] == 1) { + Edge *e = [sel anyObject]; + [edgePropDelegate setEdge:e]; + [edgeProps setData:[e data]]; + gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (edgePropsExpander)), TRUE); + if ([e hasEdgeNode]) { + gtk_toggle_button_set_mode (edgeNodeToggle, TRUE); + gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), [[[e edgeNode] label] UTF8String]); + [edgeNodeProps setData:[[e edgeNode] data]]; + gtk_widget_set_sensitive (edgeNodePropsWidget, TRUE); + } else { + gtk_toggle_button_set_mode (edgeNodeToggle, FALSE); + gtk_entry_set_text (GTK_ENTRY (edgeNodeLabelEntry), ""); + [edgeNodeProps setData:nil]; + gtk_widget_set_sensitive (edgeNodePropsWidget, FALSE); + } + } else { + [edgePropDelegate setEdge:nil]; + [edgeProps setData:nil]; + [edgeNodeProps setData:nil]; + gtk_entry_set_text (nodeLabelEntry, ""); + gtk_widget_set_sensitive (gtk_bin_get_child (GTK_BIN (edgePropsExpander)), FALSE); + } + + blockUpdates = NO; +} + +- (GtkExpander*) _addExpanderWithName:(const gchar*)name contents:(GtkWidget*)contents { + GtkWidget *exp = gtk_expander_new (name); + gtk_box_pack_start (GTK_BOX (propertiesPane), + exp, + FALSE, // expand + TRUE, // fill + 0); // padding + gtk_widget_show (exp); + gtk_container_add (GTK_CONTAINER (exp), contents); + gtk_widget_show (contents); + return GTK_EXPANDER (exp); +} + +@end + +// }}} +// {{{ Delegates + +@implementation GraphPropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + } + return self; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; +} +- (BOOL)startEdit { + if ([doc graph] != nil) { + [doc startChangeGraphProperties]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endChangeGraphProperties]; +} +- (void)cancelEdit { + [doc cancelChangeGraphProperties]; +} +@end + +@implementation NodePropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + node = nil; + } + return self; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; + node = nil; +} +- (void) setNode:(Node*)n { + node = n; +} +- (BOOL)startEdit { + if (node != nil) { + [doc startModifyNode:node]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endModifyNode]; +} +- (void)cancelEdit { + [doc cancelModifyNode]; +} +@end + +@implementation EdgePropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + edge = nil; + } + return self; +} +- (void) setDocument:(TikzDocument*)d { + doc = d; + edge = nil; +} +- (void) setEdge:(Edge*)e { + edge = e; +} +- (BOOL)startEdit { + if (edge != nil) { + [doc startModifyEdge:edge]; + return YES; + } + return NO; +} +- (void)endEdit { + [doc endModifyEdge]; +} +- (void)cancelEdit { + [doc cancelModifyEdge]; +} +@end + +// }}} +// {{{ GTK+ helpers + +static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry) { + GtkBox *box = GTK_BOX (gtk_hbox_new (FALSE, 0)); + GtkWidget *label = gtk_label_new (labelText); + gtk_widget_show (label); + GtkWidget *entryWidget = gtk_entry_new (); + gtk_widget_show (entryWidget); + // container widget expand fill pad + gtk_box_pack_start (box, label, FALSE, TRUE, 5); + gtk_box_pack_start (box, entryWidget, FALSE, TRUE, 0); + if (entry) + *entry = GTK_ENTRY (entryWidget); + return GTK_WIDGET (box); +} + +static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry) { + GtkBox *box = GTK_BOX (gtk_vbox_new (FALSE, 0)); + + GtkWidget *labelWidget = createLabelledEntry ("Label", labelEntry); + gtk_widget_show (labelWidget); + // container widget expand fill pad + gtk_box_pack_start (box, labelWidget, FALSE, TRUE, 0); + gtk_box_pack_start (box, [props widget], FALSE, TRUE, 0); + gtk_widget_show ([props widget]); + return GTK_WIDGET (box); +} + +// }}} +// {{{ GTK+ callbacks + +static void node_label_changed_cb (GtkEditable *editable, PropertyPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + [pane nodeLabelEdited:newValue]; + + [pool drain]; +} + +static void edge_node_label_changed_cb (GtkEditable *editable, PropertyPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (editable))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + [pane edgeNodeLabelEdited:newValue]; + + [pool drain]; +} + +static void edge_node_toggled_cb (GtkToggleButton *toggle, PropertyPane *pane) { + if (!gtk_widget_is_sensitive (GTK_WIDGET (toggle))) { + // clearly wasn't the user editing + return; + } + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + gboolean newValue = gtk_toggle_button_get_mode (toggle); + [pane edgeNodeToggled:newValue]; + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/RecentManager.h b/tikzit/src/linux/RecentManager.h new file mode 100644 index 0000000..e2c2793 --- /dev/null +++ b/tikzit/src/linux/RecentManager.h @@ -0,0 +1,30 @@ +/* + * 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 "TZFoundation.h" + +@interface RecentManager: NSObject { +} + ++ (RecentManager*) defaultManager; + +- (void)addRecentFile:(NSString*)path; +- (void)removeRecentFile:(NSString*)path; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/RecentManager.m b/tikzit/src/linux/RecentManager.m new file mode 100644 index 0000000..c6074c6 --- /dev/null +++ b/tikzit/src/linux/RecentManager.m @@ -0,0 +1,74 @@ +/* + * 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 "RecentManager.h" +#import <gtk/gtk.h> + +static RecentManager *defMan = nil; + +@implementation RecentManager +- (id) init { + self = [super init]; + return self; +} + ++ (RecentManager*) defaultManager { + if (defMan == nil) { + defMan = [[self alloc] init]; + } + return defMan; +} + +- (void)addRecentFile:(NSString*)path { + NSError *error = nil; + gchar *uri = [path glibUriWithError:&error]; + if (error) { + logError (error, @"Could not add recent file"); + return; + } + + GtkRecentData recent_data; + recent_data.display_name = NULL; + recent_data.description = NULL; + recent_data.mime_type = "text/x-tikz"; + recent_data.app_name = (gchar *) g_get_application_name (); + recent_data.app_exec = g_strjoin (" ", g_get_prgname (), "%u", NULL); + recent_data.groups = NULL; + recent_data.is_private = FALSE; + + gtk_recent_manager_add_full (gtk_recent_manager_get_default(), uri, &recent_data); + + g_free (uri); + g_free (recent_data.app_exec); +} + +- (void)removeRecentFile:(NSString*)path { + NSError *error = nil; + gchar *uri = [path glibUriWithError:&error]; + if (error) { + logError (error, @"Could not remove recent file"); + return; + } + + gtk_recent_manager_remove_item (gtk_recent_manager_get_default(), uri, NULL); + + g_free (uri); +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Shape+Render.h b/tikzit/src/linux/Shape+Render.h new file mode 100644 index 0000000..a744c77 --- /dev/null +++ b/tikzit/src/linux/Shape+Render.h @@ -0,0 +1,29 @@ +/* + * 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 "TZFoundation.h" +#import "Shape.h" +#import "RenderContext.h" +#import "Surface.h" + +@interface Shape (Render) + +- (void) drawPathWithTransform:(Transformer*)transform andContext:(id<RenderContext>)context; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Shape+Render.m b/tikzit/src/linux/Shape+Render.m new file mode 100644 index 0000000..924bb24 --- /dev/null +++ b/tikzit/src/linux/Shape+Render.m @@ -0,0 +1,57 @@ +/* + * 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 "Shape+Render.h" + +#import "Edge.h" + +// we use cairo for finding the bounding box etc. +#import <cairo/cairo.h> + +@implementation Shape (Render) + +- (void) drawPathWithTransform:(Transformer*)transform andContext:(id<RenderContext>)context { + [context startPath]; + + for (NSArray *arr in [self paths]) { + BOOL fst = YES; + NSPoint p, cp1, cp2; + + for (Edge *e in arr) { + if (fst) { + fst = NO; + p = [transform toScreen:[[e source] point]]; + [context moveTo:p]; + } + + p = [transform toScreen:[[e target] point]]; + if ([e isStraight]) { + [context lineTo:p]; + } else { + cp1 = [transform toScreen:[e cp1]]; + cp2 = [transform toScreen:[e cp2]]; + [context curveTo:p withCp1:cp1 andCp2:cp2]; + } + } + + [context closeSubPath]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/StyleManager+Storage.h b/tikzit/src/linux/StyleManager+Storage.h new file mode 100644 index 0000000..1727786 --- /dev/null +++ b/tikzit/src/linux/StyleManager+Storage.h @@ -0,0 +1,26 @@ +/* + * 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 "TZFoundation.h" +#import "StyleManager.h" + +@interface StyleManager (Storage) +- (void) loadStylesUsingConfigurationName:(NSString*)name; +- (void) saveStylesUsingConfigurationName:(NSString*)name; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/StyleManager+Storage.m b/tikzit/src/linux/StyleManager+Storage.m new file mode 100644 index 0000000..112b885 --- /dev/null +++ b/tikzit/src/linux/StyleManager+Storage.m @@ -0,0 +1,81 @@ +/* + * 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+Storage.h" +#import "Configuration.h" +#import "NodeStyle+Storage.h" +#import "EdgeStyle+Storage.h" + +static NSString *nodeStyleGroupPrefix = @"Style "; +static NSString *edgeStyleGroupPrefix = @"EdgeStyle "; + +@implementation StyleManager (Storage) + +- (void) loadStylesUsingConfigurationName:(NSString*)name { + if (![Configuration configurationExistsWithName:name]) { + return; + } + NSError *error = nil; + Configuration *stylesConfig = [Configuration configurationWithName:name loadError:&error]; + if (error != nil) { + logError (error, @"Could not load styles configuration"); + // stick with the default config + return; + } + NSArray *groups = [stylesConfig groups]; + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[groups count]]; + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[groups count]]; + + for (NSString *groupName in groups) { + if ([groupName hasPrefix:nodeStyleGroupPrefix]) { + NodeStyle *style = [[NodeStyle alloc] initFromConfigurationGroup:groupName config:stylesConfig]; + [ns addObject:style]; + } else if ([groupName hasPrefix:edgeStyleGroupPrefix]) { + EdgeStyle *style = [[EdgeStyle alloc] initFromConfigurationGroup:groupName config:stylesConfig]; + [es addObject:style]; + } + } + + [self _setNodeStyles:ns]; + [self _setEdgeStyles:es]; +} + +- (void) saveStylesUsingConfigurationName:(NSString*)name { + NSError *error = nil; + Configuration *stylesConfig = [Configuration emptyConfigurationWithName:name]; + NSArray *ns = [self nodeStyles]; + NSArray *es = [self edgeStyles]; + NSUInteger length = [ns count]; + for (int i = 0; i < length; ++i) { + NodeStyle *style = [ns objectAtIndex:i]; + NSString *groupName = [NSString stringWithFormat:@"%@%d", nodeStyleGroupPrefix, i]; + [style storeToConfigurationGroup:groupName config:stylesConfig]; + } + length = [es count]; + for (int i = 0; i < length; ++i) { + EdgeStyle *style = [es objectAtIndex:i]; + NSString *groupName = [NSString stringWithFormat:@"%@%d", edgeStyleGroupPrefix, i]; + [style storeToConfigurationGroup:groupName config:stylesConfig]; + } + if (![stylesConfig writeToStoreWithError:&error]) { + logError (error, @"Could not write styles configuration"); + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/Surface.h b/tikzit/src/linux/Surface.h new file mode 100644 index 0000000..d2a0dba --- /dev/null +++ b/tikzit/src/linux/Surface.h @@ -0,0 +1,90 @@ +/* + * 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 "TZFoundation.h" +#import "RenderContext.h" +#import "Transformer.h" + +@protocol Surface; + +@protocol RenderDelegate +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface; +@end + +/** + * Represents a surface that can be rendered to + * + * This protocol should be implemented by drawing surfaces. It + * provides geometry information and methods to invalidate + * regions of the surface, triggering a redraw. + * + * The surface should send a "SurfaceSizeChanged" notification + * when the width or height changes. + */ +@protocol Surface + +/** + * The width of the surface, in surface units + * + * The surface should send a "SurfaceSizeChanged" notification + * when this property changes. + */ +@property (readonly) int width; +/** + * The height of the surface, in surface units + * + * The surface should send a "SurfaceSizeChanged" notification + * when this property changes. + */ +@property (readonly) int height; +/** + * The transformer that converts between graph units and surface units + */ +@property (readonly) Transformer *transformer; +/** + * The render delegate. + * + * This will be used to redraw (parts of) the surface when necessary. + */ +@property (assign) id<RenderDelegate> renderDelegate; + +/** + * Create a render context for the surface. + */ +- (id<RenderContext>) createRenderContext; +/** + * Invalidate a portion of the surface. + * + * This will request that part of the surface be redrawn. + */ +- (void) invalidateRect:(NSRect)rect; +/** + * Invalidate the whole surface. + * + * This will request that the whole surface be redrawn. + */ +- (void) invalidate; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; +- (void) zoomInAboutPoint:(NSPoint)p; +- (void) zoomOutAboutPoint:(NSPoint)p; +- (void) zoomResetAboutPoint:(NSPoint)p; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/TZFoundation.h b/tikzit/src/linux/TZFoundation.h new file mode 100644 index 0000000..6f42700 --- /dev/null +++ b/tikzit/src/linux/TZFoundation.h @@ -0,0 +1,28 @@ +/* + * 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 <glib.h> + +#import "NSError+Glib.h" +#import "NSError+Tikzit.h" +#import "NSFileManager+Glib.h" +#import "NSFileManager+Utils.h" +#import "NSString+Glib.h" +#import "NSString+LatexConstants.h" + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/TikzDocument.h b/tikzit/src/linux/TikzDocument.h new file mode 100644 index 0000000..5ab5f30 --- /dev/null +++ b/tikzit/src/linux/TikzDocument.h @@ -0,0 +1,156 @@ +// +// TikzDocument.h +// TikZiT +// +// Copyright 2010 Chris Heunen +// 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 "TZFoundation.h" +#import <Graph.h> +#import "PickSupport.h" +#import "StyleManager.h" + +@interface TikzDocument : NSObject { + StyleManager *styleManager; + Graph *graph; + PickSupport *pickSupport; + NSUndoManager *undoManager; + NSString *tikz; + NSString *path; + NSSet *nodesetBeingModified; + BasicMapTable *nodesetBeingModifiedOldCopy; + NSPoint currentNodeShift; + Node *nodeBeingModified; + Node *nodeBeingModifiedOldCopy; + Edge *edgeBeingModified; + Edge *edgeBeingModifiedOldCopy; + NSRect oldGraphBounds; + GraphElementData *oldGraphData; + BOOL hasChanges; +} + ++ (TikzDocument*) documentWithStyleManager:(StyleManager*)manager; ++ (TikzDocument*) documentWithGraph:(Graph*)g styleManager:(StyleManager*)manager; ++ (TikzDocument*) documentWithTikz:(NSString*)t styleManager:(StyleManager*)manager; ++ (TikzDocument*) documentFromFile:(NSString*)path styleManager:(StyleManager*)manager error:(NSError**)error; + +- (id) initWithStyleManager:(StyleManager*)manager; +- (id) initWithGraph:(Graph*)g styleManager:(StyleManager*)manager; +- (id) initWithTikz:(NSString*)t styleManager:(StyleManager*)manager; +- (id) initFromFile:(NSString*)path styleManager:(StyleManager*)manager error:(NSError**)error; + +- (Graph*) graph; +- (PickSupport*) pickSupport; +- (NSString*) path; +- (NSString*) name; +- (NSString*) suggestedFileName; +- (BOOL) hasUnsavedChanges; + +- (StyleManager*) styleManager; +- (void) setStyleManager:(StyleManager*)manager; + +- (NSString*) tikz; +- (BOOL) setTikz:(NSString*)tikz; + +- (Graph*) cutSelection; +- (Graph*) copySelection; +- (void) paste:(Graph*)graph; +- (void) pasteFromTikz:(NSString*)tikz; + +// some convenience methods: +- (BOOL) isNodeSelected:(Node*)node; +- (BOOL) isEdgeSelected:(Edge*)edge; +- (NSEnumerator*) nodeEnumerator; +- (NSEnumerator*) edgeEnumerator; + +- (BOOL) canUndo; +- (void) undo; +- (BOOL) canRedo; +- (void) redo; +- (NSString*) undoName; +- (NSString*) redoName; + +- (void) startUndoGroup; +- (void) nameAndEndUndoGroup:(NSString*)nm; +- (void) endUndoGroup; + +- (void) startModifyNode:(Node*)node; +- (void) modifyNodeCheckPoint; +- (void) endModifyNode; +- (void) cancelModifyNode; + +- (void) startModifyNodes:(NSSet*)nodes; +- (void) modifyNodesCheckPoint; +- (void) endModifyNodes; +- (void) cancelModifyNodes; + +- (void) startShiftNodes:(NSSet*)nodes; +- (void) shiftNodesUpdate:(NSPoint)shiftChange; +- (void) endShiftNodes; +- (void) cancelShiftNodes; + +- (void) startModifyEdge:(Edge*)edge; +- (void) modifyEdgeCheckPoint; +- (void) endModifyEdge; +- (void) cancelModifyEdge; + +- (void) startChangeBoundingBox; +- (void) changeBoundingBoxCheckPoint; +- (void) endChangeBoundingBox; +- (void) cancelChangeBoundingBox; + +- (void) startChangeGraphProperties; +- (void) changeGraphPropertiesCheckPoint; +- (void) endChangeGraphProperties; +- (void) cancelChangeGraphProperties; + +- (void) removeSelected; +- (void) addNode:(Node*)node; +/*! + * Convenience function to add a node in the active style + * at the given point. + * + * @param pos the position (in graph co-ordinates) of the new node + * @return the added node + */ +- (Node*) addNodeAt:(NSPoint)pos; +- (void) removeNode:(Node*)node; +- (void) addEdge:(Edge*)edge; +- (void) removeEdge:(Edge*)edge; +/*! + * Convenience function to add an edge in the active style + * between the given nodes. + * + * @param source the source node + * @param target the target node + * @return the added edge + */ +- (Edge*) addEdgeFrom:(Node*)source to:(Node*)target; +- (void) shiftSelectedNodesByPoint:(NSPoint)offset; +- (void) insertGraph:(Graph*)g; +- (void) flipSelectedNodesHorizontally; +- (void) flipSelectedNodesVertically; + +- (BOOL) saveCopyToPath: (NSString*)path error: (NSError**)error; +- (BOOL) saveToPath: (NSString*)path error: (NSError**)error; +- (BOOL) save: (NSError**)error; + +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/TikzDocument.m b/tikzit/src/linux/TikzDocument.m new file mode 100644 index 0000000..43001cc --- /dev/null +++ b/tikzit/src/linux/TikzDocument.m @@ -0,0 +1,773 @@ +// +// TikzDocument.h +// TikZiT +// +// Copyright 2010 Chris Heunen +// 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 "TikzDocument.h" +#import "TikzGraphAssembler.h" + +@interface TikzDocument (Private) +- (void) styleRenamed:(NSNotification*)n; + +- (void) setPath:(NSString*)path; +- (void) setGraph:(Graph*)g; + +- (void) registerUndoForChange:(GraphChange*)change; +- (void) registerUndoGroupForChange:(GraphChange*)change withName:(NSString*)name; +- (void) undoGraphChange:(GraphChange*)change; +- (void) completedGraphChange:(GraphChange*)change withName:(NSString*)name; +- (void) attachStylesToGraph:(Graph*)g; + +- (void) regenerateTikz; +@end + +@implementation TikzDocument + ++ (TikzDocument*) documentWithStyleManager:(StyleManager*)manager { + return [[[TikzDocument alloc] initWithStyleManager:manager] autorelease]; +} + ++ (TikzDocument*) documentWithGraph:(Graph*)g styleManager:(StyleManager*)manager { + return [[[TikzDocument alloc] initWithGraph:g styleManager:manager] autorelease]; +} + ++ (TikzDocument*) documentWithTikz:(NSString*)t styleManager:(StyleManager*)manager { + return [[[TikzDocument alloc] initWithTikz:t styleManager:manager] autorelease]; +} + ++ (TikzDocument*) documentFromFile:(NSString*)pth styleManager:(StyleManager*)manager error:(NSError**)error { + return [[[TikzDocument alloc] initFromFile:pth styleManager:manager error:error] autorelease]; +} + + +- (id) initWithStyleManager:(StyleManager*)manager { + self = [self initWithGraph:[Graph graph] styleManager:manager]; + return self; +} + +- (id) initWithGraph:(Graph*)g styleManager:(StyleManager*)manager { + self = [super init]; + + if (self) { + graph = nil; + styleManager = [manager retain]; + pickSupport = [[PickSupport alloc] init]; + undoManager = [[NSUndoManager alloc] init]; + [undoManager setGroupsByEvent:NO]; + tikz = nil; + path = nil; + nodesetBeingModified = nil; + nodesetBeingModifiedOldCopy = nil; + nodeBeingModified = nil; + nodeBeingModifiedOldCopy = nil; + edgeBeingModified = nil; + edgeBeingModifiedOldCopy = nil; + + [undoManager disableUndoRegistration]; + [self setGraph:g]; + [undoManager enableUndoRegistration]; + + hasChanges = NO; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"NodeStyleRenamed" + object:styleManager]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"EdgeStyleRenamed" + object:styleManager]; + } + + return self; +} + +- (id) initWithTikz:(NSString*)t styleManager:(StyleManager*)manager { + self = [self initWithStyleManager:manager]; + + if (self) { + [undoManager disableUndoRegistration]; + [self setTikz:t]; + [undoManager enableUndoRegistration]; + hasChanges = NO; + } + + return self; +} + +- (id) initFromFile:(NSString*)pth styleManager:(StyleManager*)manager error:(NSError**)error { + NSStringEncoding enc; // we can't pass in NULL here... + NSString *t = [NSString stringWithContentsOfFile:pth + usedEncoding:&enc error:error]; + if (t == nil) { + [self release]; + return nil; + } + + self = [self initWithTikz:t styleManager:manager]; + + if (self) { + [self setPath:pth]; + } + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [styleManager release]; + [graph release]; + [pickSupport release]; + [undoManager release]; + [tikz release]; + [path release]; + [nodesetBeingModified release]; + [nodesetBeingModifiedOldCopy release]; + [nodeBeingModified release]; + [nodeBeingModifiedOldCopy release]; + [edgeBeingModified release]; + [edgeBeingModifiedOldCopy release]; + [oldGraphData release]; + [super dealloc]; +} + +- (Graph*) graph { + return graph; +} + +- (PickSupport*) pickSupport { + return pickSupport; +} + +- (NSString*) path { + return path; +} + +- (NSString*) name { + if (path) { + return [[NSFileManager defaultManager] displayNameAtPath: path]; + } else { + return @"Untitled"; + } +} + +- (NSString*) suggestedFileName { + if (path) { + return [path lastPathComponent]; + } else { + return @"untitled.tikz"; + } +} + +- (BOOL) hasUnsavedChanges { + return hasChanges; +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)manager { + StyleManager *oldManager = styleManager; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:nil + object:oldManager]; + + styleManager = [manager retain]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"NodeStyleRenamed" + object:styleManager]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(styleRenamed:) + name:@"EdgeStyleRenamed" + object:styleManager]; + + [self attachStylesToGraph:graph]; + [oldManager release]; +} + +- (void) postGraphReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphReplaced" object:self]; +} + +- (void) postGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChanged" object:self userInfo:info]; +} + +- (void) postIncompleteGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphBeingChanged" object:self userInfo:info]; +} + +- (void) postCancelledGraphChange:(GraphChange*)change { + NSDictionary *info = [NSDictionary dictionaryWithObject:change forKey:@"change"]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChangeCancelled" object:self userInfo:info]; +} + +- (void) postTikzChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"TikzChanged" object:self]; +} + +- (void) postParseError { + [[NSNotificationCenter defaultCenter] postNotificationName:@"ParseError" object:self]; +} + +- (void) postUndoStackChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"UndoStackChanged" object:self]; +} + +- (NSString*) tikz { + return tikz; +} + +- (BOOL) setTikz:(NSString*)t { + if (t == nil) { + t = [NSString string]; + } + if (t == tikz || [t isEqual:tikz]) { + return YES; + } + + TikzGraphAssembler *a = [TikzGraphAssembler assembler]; + BOOL success = [a parseTikz:t]; + if (success) { + // setTikz actually generates a graph from the tikz, + // and generates the final tikz from that + [self startUndoGroup]; + [self setGraph:[a graph]]; + [self nameAndEndUndoGroup:@"Update tikz"]; + } else { + [self postParseError]; + } + + return success; +} + +- (Graph*) cutSelection { + Graph *selection = [self copySelection]; + [self startUndoGroup]; + [self removeSelected]; + [self nameAndEndUndoGroup:@"Cut"]; + return selection; +} + +- (Graph*) copySelection { + return [[graph copyOfSubgraphWithNodes:[pickSupport selectedNodes]] autorelease]; +} + +- (void) paste:(Graph*)g { + if (g == nil || [[g nodes] count] == 0) { + // nothing to paste + return; + } + + // place to the right of the existing graph + NSRect bounds = [graph bounds]; + NSRect gBounds = [g bounds]; + float dx = NSMaxX (bounds) - gBounds.origin.x + 0.5f; + [g shiftNodes:[g nodes] byPoint:NSMakePoint (dx, 0)]; + + GraphChange *change = [graph insertGraph:g]; + [self completedGraphChange:change withName:@"Paste"]; + + // select everything from the clipboard + [pickSupport deselectAllEdges]; + [pickSupport selectAllNodes:[g nodes] replacingSelection:YES]; +} + +- (void) pasteFromTikz:(NSString*)t { + TikzGraphAssembler *a = [TikzGraphAssembler assembler]; + if ([a parseTikz:t]) { + Graph *clipboard = [a graph]; + [self attachStylesToGraph:clipboard]; + [self paste:clipboard]; + } +} + +- (BOOL) isNodeSelected:(Node*)node { + return [pickSupport isNodeSelected:node]; +} + +- (BOOL) isEdgeSelected:(Edge*)edge { + return [pickSupport isEdgeSelected:edge]; +} + +- (NSEnumerator*) nodeEnumerator { + return [[graph nodes] objectEnumerator]; +} + +- (NSEnumerator*) edgeEnumerator { + return [[graph edges] objectEnumerator]; +} + +- (BOOL) canUndo { + return [undoManager canUndo]; +} + +- (void) undo { + [undoManager undo]; + [self postUndoStackChanged]; +} + +- (BOOL) canRedo { + return [undoManager canRedo]; +} + +- (void) redo { + [undoManager redo]; + [self postUndoStackChanged]; +} + +- (NSString*) undoName { + return [undoManager undoActionName]; +} + +- (NSString*) redoName { + return [undoManager redoActionName]; +} + +- (void) startUndoGroup { + [undoManager beginUndoGrouping]; +} + +- (void) nameAndEndUndoGroup:(NSString*)nm { + [undoManager setActionName:nm]; + [undoManager endUndoGrouping]; + [self postUndoStackChanged]; +} + +- (void) endUndoGroup { + [undoManager endUndoGrouping]; + [self postUndoStackChanged]; +} + +- (void) startModifyNode:(Node*)node { + if (nodeBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node"]; + } + nodeBeingModified = [node retain]; + nodeBeingModifiedOldCopy = [node copy]; +} + +- (void) modifyNodeCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfNode:nodeBeingModified + fromOld:nodeBeingModifiedOldCopy + toNew:[[nodeBeingModified copy] autorelease]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifySequence:(GraphChange*)change withName:(NSString*)chName cancelled:(BOOL)cancelled { + if (cancelled) { + change = [change invert]; + [graph applyGraphChange:change]; + [self regenerateTikz]; + [self postCancelledGraphChange:change]; + } else { + [self registerUndoGroupForChange:change withName:chName]; + [self regenerateTikz]; + [self postGraphChange:change]; + } +} + +- (void) _finishModifyNodeCancelled:(BOOL)cancelled { + if (nodeBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node"]; + } + + GraphChange *change = [GraphChange propertyChangeOfNode:nodeBeingModified + fromOld:nodeBeingModifiedOldCopy + toNew:[[nodeBeingModified copy] autorelease]]; + [self _finishModifySequence:change withName:@"Modify node" cancelled:cancelled]; + + [nodeBeingModified release]; + nodeBeingModified = nil; + [nodeBeingModifiedOldCopy release]; + nodeBeingModifiedOldCopy = nil; +} + +- (void) endModifyNode { [self _finishModifyNodeCancelled:NO]; } +- (void) cancelModifyNode { [self _finishModifyNodeCancelled:YES]; } + +- (void) startModifyNodes:(NSSet*)nodes { + if (nodesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node set"]; + } + + nodesetBeingModified = [nodes copy]; + nodesetBeingModifiedOldCopy = [[Graph nodeTableForNodes:nodes] retain]; +} + +- (void) modifyNodesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfNodesFromOldCopies:nodesetBeingModifiedOldCopy + toNewCopies:[Graph nodeTableForNodes:nodesetBeingModified]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyNodes:(BOOL)cancelled { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + GraphChange *change = [GraphChange propertyChangeOfNodesFromOldCopies:nodesetBeingModifiedOldCopy + toNewCopies:[Graph nodeTableForNodes:nodesetBeingModified]]; + [self _finishModifySequence:change withName:@"Modify nodes" cancelled:cancelled]; + + [nodesetBeingModified release]; + nodesetBeingModified = nil; + [nodesetBeingModifiedOldCopy release]; + nodesetBeingModifiedOldCopy = nil; +} + +- (void) endModifyNodes { [self _finishModifyNodes:NO]; } +- (void) cancelModifyNodes { [self _finishModifyNodes:YES]; } + +- (void) startShiftNodes:(NSSet*)nodes { + if (nodesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying a node set"]; + } + + nodesetBeingModified = [nodes copy]; + currentNodeShift = NSZeroPoint; +} + +- (void) shiftNodesUpdate:(NSPoint)currentShift { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + currentNodeShift = currentShift; + [self regenerateTikz]; + GraphChange *change = [GraphChange shiftNodes:nodesetBeingModified + byPoint:currentNodeShift]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishShiftNodesCancelled:(BOOL)cancelled { + if (nodesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying a node set"]; + } + + if (!NSEqualPoints (currentNodeShift, NSZeroPoint)) { + GraphChange *change = [GraphChange shiftNodes:nodesetBeingModified + byPoint:currentNodeShift]; + [self _finishModifySequence:change withName:@"Move nodes" cancelled:cancelled]; + } + + [nodesetBeingModified release]; + nodesetBeingModified = nil; +} + +- (void) endShiftNodes { [self _finishShiftNodesCancelled:NO]; } +- (void) cancelShiftNodes { [self _finishShiftNodesCancelled:YES]; } + +- (void) startModifyEdge:(Edge*)edge { + if (edgeBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying an edge"]; + } + edgeBeingModified = [edge retain]; + edgeBeingModifiedOldCopy = [edge copy]; +} + +- (void) modifyEdgeCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfEdge:edgeBeingModified + fromOld:edgeBeingModifiedOldCopy + toNew:[[edgeBeingModified copy] autorelease]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyEdgeCancelled:(BOOL)cancelled { + if (edgeBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying an edge"]; + } + + GraphChange *change = [GraphChange propertyChangeOfEdge:edgeBeingModified + fromOld:edgeBeingModifiedOldCopy + toNew:[[edgeBeingModified copy] autorelease]]; + [self _finishModifySequence:change withName:@"Modify edge" cancelled:cancelled]; + + [edgeBeingModified release]; + edgeBeingModified = nil; + [edgeBeingModifiedOldCopy release]; + edgeBeingModifiedOldCopy = nil; +} + +- (void) endModifyEdge { [self _finishModifyEdgeCancelled:NO]; } +- (void) cancelModifyEdge { [self _finishModifyEdgeCancelled:YES]; } + +- (void) startChangeBoundingBox { + oldGraphBounds = [graph boundingBox]; +} + +- (void) changeBoundingBoxCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange changeBoundingBoxFrom:oldGraphBounds + to:[graph boundingBox]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishChangeBoundingBoxCancelled:(BOOL)cancelled { + GraphChange *change = [GraphChange changeBoundingBoxFrom:oldGraphBounds + to:[graph boundingBox]]; + [self _finishModifySequence:change withName:@"Set bounding box" cancelled:cancelled]; +} +- (void) endChangeBoundingBox { [self _finishChangeBoundingBoxCancelled:NO]; } +- (void) cancelChangeBoundingBox { [self _finishChangeBoundingBoxCancelled:YES]; } + +- (void) startChangeGraphProperties { + oldGraphData = [[graph data] copy]; +} + +- (void) changeGraphPropertiesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfGraphFrom:oldGraphData + to:[graph data]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishChangeGraphPropertiesCancelled:(BOOL)cancelled { + GraphChange *change = [GraphChange propertyChangeOfGraphFrom:oldGraphData + to:[graph data]]; + [self _finishModifySequence:change withName:@"Change graph properties" cancelled:cancelled]; + [oldGraphData release]; + oldGraphData = nil; +} +- (void) endChangeGraphProperties { [self _finishChangeGraphPropertiesCancelled:NO]; } +- (void) cancelChangeGraphProperties { [self _finishChangeGraphPropertiesCancelled:YES]; } + +- (void) removeSelected { + NSUInteger selEdges = [[pickSupport selectedEdges] count]; + NSUInteger selNodes = [[pickSupport selectedNodes] count]; + + if (selEdges == 0 && selNodes == 0) { + return; + } + + NSString *actionName = @"Remove selection"; + + [self startUndoGroup]; + if (selEdges > 0) { + GraphChange *change = [graph removeEdges:[pickSupport selectedEdges]]; + [self registerUndoForChange:change]; + [pickSupport deselectAllEdges]; + [self postGraphChange:change]; + } else { + actionName = (selNodes == 1 ? @"Remove node" : @"Remove nodes"); + } + if (selNodes > 0) { + GraphChange *change = [graph removeNodes:[pickSupport selectedNodes]]; + [self registerUndoForChange:change]; + [pickSupport deselectAllNodes]; + [self postGraphChange:change]; + } else { + actionName = (selEdges == 1 ? @"Remove edge" : @"Remove edges"); + } + [self nameAndEndUndoGroup:actionName]; + [self regenerateTikz]; +} + +- (void) addNode:(Node*)node { + GraphChange *change = [graph addNode:node]; + [self completedGraphChange:change withName:@"Add node"]; +} + +- (Node*) addNodeAt:(NSPoint)pos { + Node *node = [Node nodeWithPoint:pos]; + [node setStyle:[styleManager activeNodeStyle]]; + [self addNode:node]; + return node; +} + +- (void) removeNode:(Node*)node { + [pickSupport deselectNode:node]; + GraphChange *change = [graph removeNode:node]; + [self completedGraphChange:change withName:@"Remove node"]; +} + +- (void) addEdge:(Edge*)edge { + GraphChange *change = [graph addEdge:edge]; + [self completedGraphChange:change withName:@"Add edge"]; +} + +- (void) removeEdge:(Edge*)edge { + [pickSupport deselectEdge:edge]; + GraphChange *change = [graph removeEdge:edge]; + [self completedGraphChange:change withName:@"Remove edge"]; +} + +- (Edge*) addEdgeFrom:(Node*)source to:(Node*)target { + Edge *edge = [Edge edgeWithSource:source andTarget:target]; + [edge setStyle:[styleManager activeEdgeStyle]]; + [self addEdge:edge]; + return edge; +} + +- (void) shiftSelectedNodesByPoint:(NSPoint)offset { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph shiftNodes:[pickSupport selectedNodes] byPoint:offset]; + [self completedGraphChange:change withName:@"Move nodes"]; + } +} + +- (void) insertGraph:(Graph*)g { + GraphChange *change = [graph insertGraph:g]; + [self completedGraphChange:change withName:@"Insert graph"]; +} + +- (void) flipSelectedNodesHorizontally { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph flipHorizontalNodes:[pickSupport selectedNodes]]; + [self completedGraphChange:change withName:@"Flip nodes horizontally"]; + } +} + +- (void) flipSelectedNodesVertically { + if ([[pickSupport selectedNodes] count] > 0) { + GraphChange *change = [graph flipVerticalNodes:[pickSupport selectedNodes]]; + [self completedGraphChange:change withName:@"Flip nodes vertically"]; + } +} + +- (BOOL) saveCopyToPath: (NSString*)p error: (NSError**)error { + if (!p) { + [NSException raise:@"No document path" format:@"No path given"]; + } + // we use glib for writing the file, because GNUStep sucks in this regard + // (older versions don't have -[NSString writeToFile:atomically:encoding:error:]) + GError *gerror = NULL; + gchar *filename = [p glibFilename]; + BOOL success = g_file_set_contents (filename, [tikz UTF8String], -1, &gerror) ? YES : NO; + if (gerror) { + GErrorToNSError (gerror, error); + g_error_free (gerror); + } + g_free (filename); + return success; +} + +- (BOOL) saveToPath: (NSString*)p error: (NSError**)error { + BOOL success = [self saveCopyToPath:p error:error]; + if (success) { + [self setPath:p]; + hasChanges = NO; + } + return success; +} + +- (BOOL) save: (NSError**)error { + if (!path) { + [NSException raise:@"No document path" format:@"Tried to save a document when there was no path"]; + } + return [self saveToPath:path error:error]; +} + +@end + +@implementation TikzDocument (Private) +- (void) styleRenamed:(NSNotification*)n { + [self regenerateTikz]; +} + +- (void) setPath:(NSString*)p { + [p retain]; + [path release]; + path = p; +} + +- (void) setGraph:(Graph*)g { + if (g == nil) { + g = [Graph graph]; + } + if (g == graph) { + return; + } + + [pickSupport deselectAllNodes]; + [pickSupport deselectAllEdges]; + + [self startUndoGroup]; + [undoManager registerUndoWithTarget:self selector:@selector(setGraph:) object:graph]; + [g retain]; + [graph release]; + graph = g; + + [self attachStylesToGraph:graph]; + + [self regenerateTikz]; + [self postGraphReplaced]; + [self nameAndEndUndoGroup:@"Replace graph"]; +} + +- (void) registerUndoForChange:(GraphChange*)change { + [undoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:change]; +} + +- (void) registerUndoGroupForChange:(GraphChange*)change withName:(NSString*)nm { + [self startUndoGroup]; + [self registerUndoForChange:change]; + [self nameAndEndUndoGroup:nm]; +} + +- (void) undoGraphChange:(GraphChange*)change { + GraphChange *inverse = [change invert]; + [graph applyGraphChange:inverse]; + [self startUndoGroup]; + [undoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:inverse]; + [self endUndoGroup]; + [self regenerateTikz]; + [self postGraphChange:change]; +} + +- (void) completedGraphChange:(GraphChange*)change withName:(NSString*)name { + [self registerUndoGroupForChange:change withName:name]; + [self regenerateTikz]; + [self postGraphChange:change]; +} + +- (void) attachStylesToGraph:(Graph*)g { + for (Node *n in [g nodes]) { + [n attachStyleFromTable:[styleManager nodeStyles]]; + } +} + +- (void) regenerateTikz { + [tikz release]; + tikz = [[graph tikz] retain]; + hasChanges = YES; + [self postTikzChanged]; +} +@end + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/WidgetSurface.h b/tikzit/src/linux/WidgetSurface.h new file mode 100644 index 0000000..7a62660 --- /dev/null +++ b/tikzit/src/linux/WidgetSurface.h @@ -0,0 +1,55 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import <Surface.h> + +/** + * Provides a surface for rendering to a widget. + */ +@interface WidgetSurface: NSObject <Surface> { + GtkWidget *widget; + Transformer *transformer; + id <RenderDelegate> renderDelegate; + id inputDelegate; + BOOL keepCentered; + BOOL grabsFocusOnClick; + CGFloat defaultScale; + NSSize lastKnownSize; +} + +- (id) initWithWidget:(GtkWidget*)widget; +- (GtkWidget*) widget; + +- (id) inputDelegate; +- (void) setInputDelegate:(id)delegate; + +- (BOOL) keepCentered; +- (void) setKeepCentered:(BOOL)centered; + +- (BOOL) grabsFocusOnClick; +- (void) setGrabsFocusOnClick:(BOOL)focusOnClick; + +- (CGFloat) defaultScale; +- (void) setDefaultScale:(CGFloat)scale; + +- (void) setSizeRequestWidth:(double)width height:(double)height; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/WidgetSurface.m b/tikzit/src/linux/WidgetSurface.m new file mode 100644 index 0000000..3538f72 --- /dev/null +++ b/tikzit/src/linux/WidgetSurface.m @@ -0,0 +1,539 @@ +/* + * 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 "WidgetSurface.h" +#import "gtkhelpers.h" +#import "InputDelegate.h" +#import "CairoRenderContext.h" + +// {{{ Internal interfaces +// {{{ GTK+ callbacks +static gboolean configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, WidgetSurface *surface); +static void realize_cb (GtkWidget *widget, WidgetSurface *surface); +static gboolean expose_event_cb (GtkWidget *widget, GdkEventExpose *event, WidgetSurface *surface); +static gboolean button_press_event_cb (GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface); +static gboolean button_release_event_cb (GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface); +static gboolean motion_notify_event_cb (GtkWidget *widget, GdkEventMotion *event, WidgetSurface *surface); +static gboolean key_press_event_cb (GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface); +static gboolean key_release_event_cb (GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface); +static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, WidgetSurface *surface); +// }}} + +@interface WidgetSurface (Private) +- (void) updateTransformer; +- (void) widgetSizeChanged:(NSNotification*)notification; +- (void) handleExposeEvent:(GdkEventExpose*)event; +- (void) updateLastKnownSize; +- (void) zoomTo:(CGFloat)scale aboutPoint:(NSPoint)p; +- (void) zoomTo:(CGFloat)scale; +@end +// }}} +// {{{ API +@implementation WidgetSurface + +- (id) init { + return [self initWithWidget:gtk_drawing_area_new ()]; +} + +- (id) initWithWidget:(GtkWidget*)w { + self = [super init]; + + if (self) { + widget = w; + g_object_ref_sink (G_OBJECT (widget)); + renderDelegate = nil; + inputDelegate = nil; + keepCentered = NO; + grabsFocusOnClick = NO; + defaultScale = 1.0f; + transformer = [[Transformer alloc] init]; + [transformer setFlippedAboutXAxis:YES]; + [self updateLastKnownSize]; + g_object_set (G_OBJECT (widget), "events", GDK_STRUCTURE_MASK, NULL); + g_signal_connect (widget, "expose-event", G_CALLBACK (expose_event_cb), self); + g_signal_connect (widget, "configure-event", G_CALLBACK (configure_event_cb), self); + g_signal_connect (widget, "realize", G_CALLBACK (realize_cb), self); + g_signal_connect (widget, "button-press-event", G_CALLBACK (button_press_event_cb), self); + g_signal_connect (widget, "button-release-event", G_CALLBACK (button_release_event_cb), self); + g_signal_connect (widget, "motion-notify-event", G_CALLBACK (motion_notify_event_cb), self); + g_signal_connect (widget, "key-press-event", G_CALLBACK (key_press_event_cb), self); + g_signal_connect (widget, "key-release-event", G_CALLBACK (key_release_event_cb), self); + g_signal_connect (widget, "scroll-event", G_CALLBACK (scroll_event_cb), self); + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(widgetSizeChanged:) + name:@"SurfaceSizeChanged" + object:self]; + } + + return self; +} + +- (void) invalidateRect:(NSRect)rect { + if (!NSIsEmptyRect (rect)) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + GdkRectangle g_rect = gdk_rectangle_from_ns_rect (rect); + gdk_window_invalidate_rect (window, &g_rect, TRUE); + } + } +} + +- (void) invalidate { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + GdkRegion *visible = gdk_drawable_get_visible_region (GDK_DRAWABLE (window)); + gdk_window_invalidate_region (window, visible, TRUE); + gdk_region_destroy (visible); + } +} + +- (id<RenderContext>) createRenderContext { + return [CairoRenderContext contextForWidget:widget]; +} + +- (int) width { + int width = 0; + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_drawable_get_size (window, &width, NULL); + } + return width; +} + +- (int) height { + int height = 0; + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_drawable_get_size (window, NULL, &height); + } + return height; +} + +- (void) setSizeRequestWidth:(double)width height:(double)height { + gtk_widget_set_size_request (widget, width, height); +} + +- (Transformer*) transformer { + return transformer; +} + +- (GtkWidget*) widget { + return widget; +} + +- (void) addToEventMask:(GdkEventMask)value { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask |= value; + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +- (void) removeFromEventMask:(GdkEventMask)value { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask ^= value; + if (grabsFocusOnClick) { + mask |= GDK_BUTTON_PRESS_MASK; + } + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +- (void) setRenderDelegate:(id <RenderDelegate>)delegate { + // NB: no retention! + renderDelegate = delegate; + if (renderDelegate == nil) { + [self removeFromEventMask:GDK_EXPOSURE_MASK]; + } else { + [self addToEventMask:GDK_EXPOSURE_MASK]; + } +} + +- (id) inputDelegate { + return inputDelegate; +} + +- (void) setInputDelegate:(id)delegate { + if (delegate == inputDelegate) { + return; + } + if (inputDelegate != nil) { + [self removeFromEventMask:GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_KEY_PRESS_MASK + | GDK_KEY_RELEASE_MASK]; + } + inputDelegate = delegate; + if (delegate != nil) { + GdkEventMask mask = 0; + if ([delegate respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + mask |= GDK_BUTTON_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + mask |= GDK_BUTTON_RELEASE_MASK; + } + if ([delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + mask |= GDK_BUTTON_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + mask |= GDK_POINTER_MOTION_MASK; + } + if ([delegate respondsToSelector:@selector(keyPressed:withMask:)]) { + mask |= GDK_KEY_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(keyReleased:withMask:)]) { + mask |= GDK_KEY_RELEASE_MASK; + } + [self addToEventMask:mask]; + } +} + +- (id <RenderDelegate>) renderDelegate { + return renderDelegate; +} + +- (void) setKeepCentered:(BOOL)centered { + keepCentered = centered; + [self updateTransformer]; +} + +- (BOOL) keepCentered { + return keepCentered; +} + +- (BOOL) grabsFocusOnClick { + return grabsFocusOnClick; +} + +- (void) setGrabsFocusOnClick:(BOOL)focus { + if (grabsFocusOnClick != focus) { + grabsFocusOnClick = focus; + if (grabsFocusOnClick) { + [self addToEventMask:GDK_BUTTON_PRESS_MASK]; + } else { + [self removeFromEventMask:GDK_BUTTON_PRESS_MASK]; + } + } +} + +- (CGFloat) defaultScale { + return defaultScale; +} + +- (void) setDefaultScale:(CGFloat)newDefault { + if (defaultScale != newDefault) { + CGFloat oldDefault = defaultScale; + defaultScale = newDefault; + + CGFloat scale = [transformer scale]; + scale *= (newDefault / oldDefault); + [transformer setScale:scale]; + [self invalidate]; + } +} + +- (void) zoomIn { + CGFloat scale = [transformer scale]; + scale *= 1.2f; + [self zoomTo:scale]; +} + +- (void) zoomOut { + CGFloat scale = [transformer scale]; + scale /= 1.2f; + [self zoomTo:scale]; +} + +- (void) zoomReset { + [self zoomTo:defaultScale]; +} + +- (void) zoomInAboutPoint:(NSPoint)p { + CGFloat scale = [transformer scale]; + scale *= 1.2f; + [self zoomTo:scale aboutPoint:p]; +} + +- (void) zoomOutAboutPoint:(NSPoint)p { + CGFloat scale = [transformer scale]; + scale /= 1.2f; + [self zoomTo:scale aboutPoint:p]; +} + +- (void) zoomResetAboutPoint:(NSPoint)p { + [self zoomTo:defaultScale aboutPoint:p]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [transformer release]; + g_object_unref (G_OBJECT (widget)); + + [super dealloc]; +} + +@end +// }}} +// {{{ Private +@implementation WidgetSurface (Private) +- (void) widgetSizeChanged:(NSNotification*)notification { + [self updateTransformer]; + [self updateLastKnownSize]; +} + +- (void) updateTransformer { + if (keepCentered) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + NSPoint origin; + if (lastKnownSize.width < 1 || lastKnownSize.height < 1) { + origin.x = (float)width / 2.0f; + origin.y = (float)height / 2.0f; + } else { + origin = [transformer origin]; + origin.x += ((float)width - lastKnownSize.width) / 2.0f; + origin.y += ((float)height - lastKnownSize.height) / 2.0f; + } + [transformer setOrigin:origin]; + } + } +} + +- (void) handleExposeEvent:(GdkEventExpose*)event { + if (renderDelegate != nil) { + NSRect area = gdk_rectangle_to_ns_rect (event->area); + + id<RenderContext> context = [CairoRenderContext contextForWidget:widget]; + [context rect:area]; + [context clipToPath]; + [renderDelegate renderWithContext:context onSurface:self]; + } +} + +- (void) updateLastKnownSize { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + lastKnownSize.width = (float)width; + lastKnownSize.height = (float)height; + } else { + lastKnownSize = NSZeroSize; + } +} + +- (void) zoomTo:(CGFloat)scale aboutPoint:(NSPoint)p { + NSPoint graphP = [transformer fromScreen:p]; + + [transformer setScale:scale]; + + NSPoint newP = [transformer toScreen:graphP]; + NSPoint origin = [transformer origin]; + origin.x += p.x - newP.x; + origin.y += p.y - newP.y; + [transformer setOrigin:origin]; + + [self invalidate]; +} + +- (void) zoomTo:(CGFloat)scale { + NSPoint centre = NSMakePoint (lastKnownSize.width/2.0f, lastKnownSize.height/2.0f); + [self zoomTo:scale aboutPoint:centre]; +} + +@end +// }}} +// {{{ GTK+ callbacks +static gboolean configure_event_cb(GtkWidget *widget, GdkEventConfigure *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"SurfaceSizeChanged" object:surface]; + [pool drain]; + return FALSE; +} + +static void realize_cb (GtkWidget *widget, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [surface updateTransformer]; + [pool drain]; +} + +static gboolean expose_event_cb(GtkWidget *widget, GdkEventExpose *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [surface handleExposeEvent:event]; + [pool drain]; + return FALSE; +} + +InputMask mask_from_gdk_modifier_state (GdkModifierType state) { + InputMask mask = 0; + if (state & GDK_SHIFT_MASK) { + mask |= ShiftMask; + } + if (state & GDK_CONTROL_MASK) { + mask |= ControlMask; + } + if (state & GDK_META_MASK) { + mask |= MetaMask; + } + return mask; +} + +ScrollDirection scroll_dir_from_gdk_scroll_dir (GdkScrollDirection dir) { + switch (dir) { + case GDK_SCROLL_UP: return ScrollUp; + case GDK_SCROLL_DOWN: return ScrollDown; + case GDK_SCROLL_LEFT: return ScrollLeft; + case GDK_SCROLL_RIGHT: return ScrollRight; + default: NSLog(@"Invalid scroll direction %i", (int)dir); return ScrollDown; + } +} + +MouseButton buttons_from_gdk_modifier_state (GdkModifierType state) { + MouseButton buttons = 0; + if (state & GDK_BUTTON1_MASK) { + buttons |= LeftButton; + } + if (state & GDK_BUTTON2_MASK) { + buttons |= MiddleButton; + } + if (state & GDK_BUTTON3_MASK) { + buttons |= RightButton; + } + if (state & GDK_BUTTON4_MASK) { + buttons |= Button4; + } + if (state & GDK_BUTTON5_MASK) { + buttons |= Button5; + } + return buttons; +} + +static gboolean button_press_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if ([surface grabsFocusOnClick]) { + if (!GTK_WIDGET_HAS_FOCUS (widget)) { + gtk_widget_grab_focus (widget); + } + } + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton button = (MouseButton)event->button; + InputMask mask = mask_from_gdk_modifier_state (event->state); + if (event->type == GDK_BUTTON_PRESS && [delegate respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + [delegate mousePressAt:pos withButton:button andMask:mask]; + } + if (event->type == GDK_2BUTTON_PRESS && [delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + [delegate mouseDoubleClickAt:pos withButton:button andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean button_release_event_cb(GtkWidget *widget, GdkEventButton *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton button = (MouseButton)event->button; + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate mouseReleaseAt:pos withButton:button andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + MouseButton buttons = buttons_from_gdk_modifier_state (event->state); + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate mouseMoveTo:pos withButtons:buttons andMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean key_press_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(keyPressed:withMask:)]) { + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate keyPressed:event->keyval withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean key_release_event_cb(GtkWidget *widget, GdkEventKey *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(keyReleased:withMask:)]) { + InputMask mask = mask_from_gdk_modifier_state (event->state); + [delegate keyReleased:event->keyval withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} + +static gboolean scroll_event_cb (GtkWidget *widget, GdkEventScroll *event, WidgetSurface *surface) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id delegate = [surface inputDelegate]; + if (delegate != nil) { + if ([delegate respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) { + NSPoint pos = NSMakePoint (event->x, event->y); + InputMask mask = mask_from_gdk_modifier_state (event->state); + ScrollDirection dir = scroll_dir_from_gdk_scroll_dir (event->direction); + [delegate mouseScrolledAt:pos + inDirection:dir + withMask:mask]; + } + } + + [pool drain]; + return FALSE; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/cairo_helpers.h b/tikzit/src/linux/cairo_helpers.h new file mode 100644 index 0000000..e95357b --- /dev/null +++ b/tikzit/src/linux/cairo_helpers.h @@ -0,0 +1,25 @@ +/* + * 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 "TZFoundation.h" +#import "RColor.h" +#import <cairo/cairo.h> + +void cairo_ns_rectangle (cairo_t* cr, NSRect rect); +void cairo_set_source_rcolor (cairo_t* cr, RColor color); + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/cairo_helpers.m b/tikzit/src/linux/cairo_helpers.m new file mode 100644 index 0000000..104e686 --- /dev/null +++ b/tikzit/src/linux/cairo_helpers.m @@ -0,0 +1,28 @@ +/* + * 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 "cairo_helpers.h" + +void cairo_ns_rectangle (cairo_t* cr, NSRect rect) { + cairo_rectangle (cr, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); +} + +void cairo_set_source_rcolor (cairo_t* cr, RColor color) { + cairo_set_source_rgba (cr, color.red, color.green, color.blue, color.alpha); +} + +// vim:ft=objc:sts=4:sw=4:et diff --git a/tikzit/src/linux/clipboard.h b/tikzit/src/linux/clipboard.h new file mode 100644 index 0000000..568fc50 --- /dev/null +++ b/tikzit/src/linux/clipboard.h @@ -0,0 +1,41 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import <Graph.h> + +enum { + TARGET_UTF8_STRING, + TARGET_TIKZIT_PICTURE +}; +typedef struct +{ + Graph *graph; + gchar *tikz; + gint tikz_length; +} ClipboardGraphData; + +extern GdkAtom utf8_atom; +extern GdkAtom tikzit_picture_atom; + +void clipboard_init (); +ClipboardGraphData *clipboard_graph_data_new (Graph *graph); +void clipboard_graph_data_free (ClipboardGraphData *data); +void clipboard_graph_data_convert (ClipboardGraphData *data); + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/clipboard.m b/tikzit/src/linux/clipboard.m new file mode 100644 index 0000000..7001717 --- /dev/null +++ b/tikzit/src/linux/clipboard.m @@ -0,0 +1,57 @@ +/* + * 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 "clipboard.h" + +GdkAtom utf8_atom; +GdkAtom tikzit_picture_atom; + +void clipboard_init () { + if (utf8_atom == GDK_NONE) { + utf8_atom = gdk_atom_intern ("UTF8_STRING", FALSE); + } + if (tikzit_picture_atom == GDK_NONE) { + tikzit_picture_atom = gdk_atom_intern ("TIKZITPICTURE", FALSE); + } +} + +ClipboardGraphData *clipboard_graph_data_new (Graph *graph) { + ClipboardGraphData *data = g_new (ClipboardGraphData, 1); + data->graph = [graph retain]; + data->tikz = NULL; + data->tikz_length = 0; + return data; +} + +void clipboard_graph_data_free (ClipboardGraphData *data) { + [data->graph release]; + if (data->tikz) { + g_free (data->tikz); + } + g_free (data); +} + +void clipboard_graph_data_convert (ClipboardGraphData *data) { + if (data->graph != nil && !data->tikz) { + data->tikz = g_strdup ([[data->graph tikz] UTF8String]); + data->tikz_length = strlen (data->tikz); + [data->graph release]; + data->graph = nil; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit/src/linux/gtkhelpers.h b/tikzit/src/linux/gtkhelpers.h new file mode 100644 index 0000000..418d234 --- /dev/null +++ b/tikzit/src/linux/gtkhelpers.h @@ -0,0 +1,49 @@ +// +// gtkhelpers.h +// TikZiT +// +// Copyright 2010 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 "TZFoundation.h" +#include <gtk/gtk.h> + +void gtk_table_adjust_attach (GtkTable *table, + GtkWidget *widget, + gint left_adjust, + gint right_adjust, + gint top_adjust, + gint bottom_adjust); +void gtk_table_delete_row (GtkTable *table, guint row); +void gtk_table_delete_rows (GtkTable *table, guint firstRow, guint count); + +NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end); + +void gtk_entry_set_string (GtkEntry *entry, NSString *string); +NSString * gtk_entry_get_string (GtkEntry *entry); + +GdkRectangle gdk_rectangle_from_ns_rect (NSRect rect); +NSRect gdk_rectangle_to_ns_rect (GdkRectangle rect); + +void gtk_action_set_detailed_label (GtkAction *action, const gchar *baseLabel, const gchar *actionName); + +gint tz_hijack_key_press (GtkWindow *win, + GdkEventKey *event, + gpointer user_data); + +// vim:ft=objc:sts=2:sw=2:et diff --git a/tikzit/src/linux/gtkhelpers.m b/tikzit/src/linux/gtkhelpers.m new file mode 100644 index 0000000..164228c --- /dev/null +++ b/tikzit/src/linux/gtkhelpers.m @@ -0,0 +1,244 @@ +// +// gtkhelpers.h +// TikZiT +// +// Copyright 2010 Alex Merry. All rights reserved. +// +// Some code from Glade: +// Copyright 2001 Ximian, Inc. +// +// 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 "gtkhelpers.h" +#import <gdk/gdkkeysyms.h> + +void gtk_table_adjust_attach (GtkTable *table, + GtkWidget *widget, + gint left_adjust, + gint right_adjust, + gint top_adjust, + gint bottom_adjust) { + guint top_attach; + guint bottom_attach; + guint left_attach; + guint right_attach; + GtkAttachOptions xoptions; + GtkAttachOptions yoptions; + guint xpadding; + guint ypadding; + + gtk_container_child_get (GTK_CONTAINER (table), widget, + "top-attach", &top_attach, + "bottom-attach", &bottom_attach, + "left-attach", &left_attach, + "right-attach", &right_attach, + "x-options", &xoptions, + "y-options", &yoptions, + "x-padding", &xpadding, + "y-padding", &ypadding, + NULL); + + g_object_ref (G_OBJECT (widget)); + gtk_container_remove (GTK_CONTAINER (table), widget); + gtk_table_attach (table, widget, + left_attach + left_adjust, + right_attach + right_adjust, + top_attach + top_adjust, + bottom_attach + bottom_adjust, + xoptions, + yoptions, + xpadding, + ypadding); + g_object_unref (G_OBJECT (widget)); +} + +/* + * Delete multiple table rows + */ +void gtk_table_delete_rows (GtkTable *table, guint firstRow, guint count) { + if (count == 0) { + return; + } + GtkContainer *tableC = GTK_CONTAINER (table); + + guint n_columns; + guint n_rows; + g_object_get (G_OBJECT (table), + "n-columns", &n_columns, + "n-rows", &n_rows, + NULL); + guint topBound = firstRow; + guint bottomBound = firstRow + count; + if (bottomBound > n_rows) { + bottomBound = n_rows; + count = bottomBound - topBound; + } + + GList *toBeDeleted = NULL; + GList *toBeShrunk = NULL; + /* indexed by top-attach */ + GPtrArray *toBeMoved = g_ptr_array_sized_new (n_rows - topBound); + g_ptr_array_set_size (toBeMoved, n_rows - topBound); + + GList *childIt = gtk_container_get_children (tableC); + + while (childIt) { + GtkWidget *widget = GTK_WIDGET (childIt->data); + guint top_attach; + guint bottom_attach; + gtk_container_child_get (tableC, widget, + "top-attach", &top_attach, + "bottom-attach", &bottom_attach, + NULL); + if (top_attach >= topBound && bottom_attach <= bottomBound) { + toBeDeleted = g_list_prepend (toBeDeleted, widget); + } else if (top_attach <= topBound && bottom_attach > topBound) { + toBeShrunk = g_list_prepend (toBeShrunk, widget); + } else if (top_attach > topBound) { + GList *rowList = (GList*)g_ptr_array_index (toBeMoved, top_attach - topBound); + rowList = g_list_prepend (rowList, widget); + g_ptr_array_index (toBeMoved, top_attach - topBound) = rowList; + } + childIt = childIt->next; + } + g_list_free (childIt); + + /* remove anything that is completely within the segment being deleted */ + while (toBeDeleted) { + gtk_container_remove (tableC, GTK_WIDGET (toBeDeleted->data)); + toBeDeleted = toBeDeleted->next; + } + g_list_free (toBeDeleted); + + /* shrink anything that spans the segment */ + while (toBeShrunk) { + GtkWidget *widget = GTK_WIDGET (toBeShrunk->data); + gtk_table_adjust_attach (table, widget, 0, 0, 0, -count); + toBeShrunk = toBeShrunk->next; + } + g_list_free (toBeShrunk); + + /* move everything below the segment being deleted up, in order */ + /* note that "n-rows" is not a valid "top-attach" */ + for (int offset = 0; offset < (n_rows - 1) - topBound; ++offset) { + GList *rowList = (GList *)g_ptr_array_index (toBeMoved, offset); + guint top_attach = offset + topBound; + guint overlap = bottomBound - top_attach; + while (rowList) { + GtkWidget *widget = GTK_WIDGET (rowList->data); + gtk_table_adjust_attach (table, widget, 0, 0, -offset, -(offset + overlap)); + rowList = rowList->next; + } + g_list_free (rowList); + g_ptr_array_index (toBeMoved, offset) = NULL; + } + + gtk_table_resize (table, n_rows - 1, n_columns); +} + +/* + * Delete a table row + */ +void gtk_table_delete_row (GtkTable *table, guint row) { + gtk_table_delete_rows (table, row, 1); +} + +NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end) +{ + gchar *text = gtk_editable_get_chars (editable, start, end); + NSString *string = [NSString stringWithUTF8String:text]; + g_free (text); + return string; +} + +void gtk_entry_set_string (GtkEntry *entry, NSString *string) +{ + gtk_entry_set_text (entry, string == nil ? "" : [string UTF8String]); +} + +NSString * gtk_entry_get_string (GtkEntry *entry) +{ + return [NSString stringWithUTF8String:gtk_entry_get_text (entry)]; +} + +GdkRectangle gdk_rectangle_from_ns_rect (NSRect box) { + GdkRectangle rect; + rect.x = box.origin.x; + rect.y = box.origin.y; + rect.width = box.size.width; + rect.height = box.size.height; + return rect; +} + +NSRect gdk_rectangle_to_ns_rect (GdkRectangle rect) { + NSRect result; + result.origin.x = rect.x; + result.origin.y = rect.y; + result.size.width = rect.width; + result.size.height = rect.height; + return result; +} + +void gtk_action_set_detailed_label (GtkAction *action, const gchar *baseLabel, const gchar *actionName) { + if (actionName == NULL || *actionName == '\0') { + gtk_action_set_label (action, baseLabel); + } else { + GString *label = g_string_sized_new (30); + g_string_printf(label, "%s: %s", baseLabel, actionName); + gtk_action_set_label (action, label->str); + g_string_free (label, TRUE); + } +} + +/** + * tz_hijack_key_press: + * @win: a #GtkWindow + * event: the GdkEventKey + * user_data: unused + * + * This function is meant to be attached to key-press-event of a toplevel, + * it simply allows the window contents to treat key events /before/ + * accelerator keys come into play (this way widgets dont get deleted + * when cutting text in an entry etc.). + * Creates a liststore suitable for comboboxes and such to + * chose from a variety of types. + * + * Returns: whether the event was handled + */ +gint +tz_hijack_key_press (GtkWindow *win, + GdkEventKey *event, + gpointer user_data) +{ + GtkWidget *focus_widget; + + focus_widget = gtk_window_get_focus (win); + if (focus_widget && + (event->keyval == GDK_Delete || /* Filter Delete from accelerator keys */ + ((event->state & GDK_CONTROL_MASK) && /* CTRL keys... */ + ((event->keyval == GDK_c || event->keyval == GDK_C) || /* CTRL-C (copy) */ + (event->keyval == GDK_x || event->keyval == GDK_X) || /* CTRL-X (cut) */ + (event->keyval == GDK_v || event->keyval == GDK_V) || /* CTRL-V (paste) */ + (event->keyval == GDK_a || event->keyval == GDK_A) || /* CTRL-A (select-all) */ + (event->keyval == GDK_n || event->keyval == GDK_N))))) /* CTRL-N (new document) ?? */ + { + return gtk_widget_event (focus_widget, + (GdkEvent *)event); + } + return FALSE; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/logo.h b/tikzit/src/linux/logo.h new file mode 100644 index 0000000..48a70ba --- /dev/null +++ b/tikzit/src/linux/logo.h @@ -0,0 +1,32 @@ +/* + * Copyright 2012 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/>. + */ + +#include <gdk-pixbuf/gdk-pixbuf.h> + +typedef enum { + LOGO_SIZE_16, + LOGO_SIZE_24, + //LOGO_SIZE_32, + LOGO_SIZE_48, + LOGO_SIZE_64, + LOGO_SIZE_128, + LOGO_SIZE_COUNT +} LogoSize; + +GdkPixbuf *get_logo (LogoSize size); + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit/src/linux/logo.m b/tikzit/src/linux/logo.m new file mode 100644 index 0000000..1bf006d --- /dev/null +++ b/tikzit/src/linux/logo.m @@ -0,0 +1,65 @@ +/* + * Copyright 2012 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 "logo.h" +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "logodata.m" +#pragma GCC diagnostic pop + +static GdkPixbuf *pixbufCache[LOGO_SIZE_COUNT]; + +GdkPixbuf *get_logo (LogoSize size) { + const GdkPixdata *data = NULL; + switch (size) { + case LOGO_SIZE_16: + data = &logo16; + break; + case LOGO_SIZE_24: + data = &logo24; + break; +/* case LOGO_SIZE_32: + data = &logo32; + break; +*/ + case LOGO_SIZE_48: + data = &logo48; + break; + case LOGO_SIZE_64: + data = &logo64; + break; + case LOGO_SIZE_128: + data = &logo128; + break; + default: + return NULL; + }; + if (pixbufCache[size]) { + g_object_ref (pixbufCache[size]); + return pixbufCache[size]; + } else { + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (data, FALSE, NULL); + pixbufCache[size] = buf; + g_object_add_weak_pointer (G_OBJECT (buf), (void**)(&(pixbufCache[size]))); + return buf; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 + diff --git a/tikzit/src/linux/main.m b/tikzit/src/linux/main.m new file mode 100644 index 0000000..f28123c --- /dev/null +++ b/tikzit/src/linux/main.m @@ -0,0 +1,75 @@ +// +// main.m +// 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "clipboard.h" +#import "logo.h" + +#import "MainWindow.h" +#import "TikzGraphAssembler.h" + +void onUncaughtException(NSException* exception) +{ + NSLog(@"uncaught exception: %@", [exception description]); +} + +int main (int argc, char *argv[]) { + NSSetUncaughtExceptionHandler(&onUncaughtException); + + [[NSAutoreleasePool alloc] init]; + + gtk_init (&argc, &argv); + + NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + +#ifndef WINDOWS + GList *icon_list = NULL; + g_list_prepend (icon_list, get_logo(LOGO_SIZE_128)); + g_list_prepend (icon_list, get_logo(LOGO_SIZE_64)); + g_list_prepend (icon_list, get_logo(LOGO_SIZE_48)); + //g_list_prepend (icon_list, get_logo(LOGO_SIZE_32)); + g_list_prepend (icon_list, get_logo(LOGO_SIZE_24)); + g_list_prepend (icon_list, get_logo(LOGO_SIZE_16)); + gtk_window_set_default_icon_list (icon_list); + GList *list_head = icon_list; + while (list_head) { + g_object_unref ((GObject*)list_head->data); + list_head = list_head->next; + } +#endif + + clipboard_init(); + [TikzGraphAssembler setup]; + MainWindow *window = [[MainWindow alloc] init]; + + [initPool drain]; + + gtk_main (); + + [window saveConfiguration]; + + return 0; +} + +// vim:ft=objc:et:sts=4:sw=4 diff --git a/tikzit/src/linux/mkdtemp.h b/tikzit/src/linux/mkdtemp.h new file mode 100644 index 0000000..65ee99e --- /dev/null +++ b/tikzit/src/linux/mkdtemp.h @@ -0,0 +1,32 @@ +/* Creating a private temporary directory. + Copyright (C) 2001-2002 Free Software Foundation, Inc. + + 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, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_MKDTEMP + +/* Get mkdtemp() declaration. */ +#include <stdlib.h> + +#else + +/* Create a unique temporary directory from TEMPLATE. + The last six characters of TEMPLATE must be "XXXXXX"; + they are replaced with a string that makes the directory name unique. + Returns TEMPLATE, or a null pointer if it cannot get a unique name. + The directory is created mode 700. */ +extern char * mkdtemp (char *template); + +#endif diff --git a/tikzit/src/linux/mkdtemp.m b/tikzit/src/linux/mkdtemp.m new file mode 100644 index 0000000..0866e54 --- /dev/null +++ b/tikzit/src/linux/mkdtemp.m @@ -0,0 +1,204 @@ +/* Copyright (C) 1999, 2001-2003 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* Extracted from misc/mkdtemp.c and sysdeps/posix/tempname.c. */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Specification. */ +#include "mkdtemp.h" + +#include <errno.h> +#ifndef __set_errno +# define __set_errno(Val) errno = (Val) +#endif + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <stdio.h> +#ifndef TMP_MAX +# define TMP_MAX 238328 +#endif + +#if HAVE_STDINT_H || _LIBC +# include <stdint.h> +#endif +#if HAVE_INTTYPES_H +# include <inttypes.h> +#endif + +#if HAVE_UNISTD_H || _LIBC +# include <unistd.h> +#endif + +#if HAVE_GETTIMEOFDAY || _LIBC +# if HAVE_SYS_TIME_H || _LIBC +# include <sys/time.h> +# endif +#else +# if HAVE_TIME_H || _LIBC +# include <time.h> +# endif +#endif + +#include <sys/stat.h> +#if STAT_MACROS_BROKEN +# undef S_ISDIR +#endif +#if !defined S_ISDIR && defined S_IFDIR +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !S_IRUSR && S_IREAD +# define S_IRUSR S_IREAD +#endif +#if !S_IRUSR +# define S_IRUSR 00400 +#endif +#if !S_IWUSR && S_IWRITE +# define S_IWUSR S_IWRITE +#endif +#if !S_IWUSR +# define S_IWUSR 00200 +#endif +#if !S_IXUSR && S_IEXEC +# define S_IXUSR S_IEXEC +#endif +#if !S_IXUSR +# define S_IXUSR 00100 +#endif + +#ifdef __MINGW32__ +/* mingw's mkdir() function has 1 argument, but we pass 2 arguments. + Therefore we have to disable the argument count checking. */ +# define mkdir ((int (*)()) mkdir) +#endif + +#if !_LIBC +# define __getpid getpid +# define __gettimeofday gettimeofday +# define __mkdir mkdir +#endif + +/* Use the widest available unsigned type if uint64_t is not + available. The algorithm below extracts a number less than 62**6 + (approximately 2**35.725) from uint64_t, so ancient hosts where + uintmax_t is only 32 bits lose about 3.725 bits of randomness, + which is better than not having mkstemp at all. */ +#if !defined UINT64_MAX && !defined uint64_t +# define uint64_t uintmax_t +#endif + +/* These are the characters used in temporary filenames. */ +static const char letters[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + +/* Generate a temporary file name based on TMPL. TMPL must match the + rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed + does not exist at the time of the call to __gen_tempname. TMPL is + overwritten with the result. + + KIND is: + __GT_DIR: create a directory, which will be mode 0700. + + We use a clever algorithm to get hard-to-predict names. */ +static int +gen_tempname (char *tmpl) +{ + int len; + char *XXXXXX; + static uint64_t value; + uint64_t random_time_bits; + int count, fd = -1; + int save_errno = errno; + + len = strlen (tmpl); + if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) + { + __set_errno (EINVAL); + return -1; + } + + /* This is where the Xs start. */ + XXXXXX = &tmpl[len - 6]; + + /* Get some more or less random data. */ +#ifdef RANDOM_BITS + RANDOM_BITS (random_time_bits); +#else +# if HAVE_GETTIMEOFDAY || _LIBC + { + struct timeval tv; + __gettimeofday (&tv, NULL); + random_time_bits = ((uint64_t) tv.tv_usec << 16) ^ tv.tv_sec; + } +# else + random_time_bits = time (NULL); +# endif +#endif + value += random_time_bits ^ __getpid (); + + for (count = 0; count < TMP_MAX; value += 7777, ++count) + { + uint64_t v = value; + + /* Fill in the random bits. */ + XXXXXX[0] = letters[v % 62]; + v /= 62; + XXXXXX[1] = letters[v % 62]; + v /= 62; + XXXXXX[2] = letters[v % 62]; + v /= 62; + XXXXXX[3] = letters[v % 62]; + v /= 62; + XXXXXX[4] = letters[v % 62]; + v /= 62; + XXXXXX[5] = letters[v % 62]; + + fd = __mkdir (tmpl, S_IRUSR | S_IWUSR | S_IXUSR); + + if (fd >= 0) + { + __set_errno (save_errno); + return fd; + } + else if (errno != EEXIST) + return -1; + } + + /* We got out of the loop because we ran out of combinations to try. */ + __set_errno (EEXIST); + return -1; +} + +/* Generate a unique temporary directory from TEMPLATE. + The last six characters of TEMPLATE must be "XXXXXX"; + they are replaced with a string that makes the filename unique. + The directory is created, mode 700, and its name is returned. + (This function comes from OpenBSD.) */ +char * +mkdtemp (char *template) +{ + if (gen_tempname (template)) + return NULL; + else + return template; +} diff --git a/tikzit/src/linux/test/linux.m b/tikzit/src/linux/test/linux.m new file mode 100644 index 0000000..ade0867 --- /dev/null +++ b/tikzit/src/linux/test/linux.m @@ -0,0 +1,27 @@ +// +// linux.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 testLinux() { + +} diff --git a/tikzit/src/linux/test/main.m b/tikzit/src/linux/test/main.m new file mode 100644 index 0000000..639a335 --- /dev/null +++ b/tikzit/src/linux/test/main.m @@ -0,0 +1,50 @@ +// +// main.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/>. +// +#include "config.h" +#import "test/test.h" +#include <string.h> +void testCommon(); + +int main(int argc, char **argv) { + if (argc == 2 && strcmp(argv[1], "--disable-color")==0) { + setColorEnabled(NO); + } else { + setColorEnabled(YES); + } + + PUTS(@""); + PUTS(@"**********************************************"); + PUTS(@"TikZiT TESTS, LINUX VERSION %@", VERSION); + PUTS(@"**********************************************"); + PUTS(@""); + + startTests(); + testCommon(); + testLinux(); + + PUTS(@""); + PUTS(@"**********************************************"); + endTests(); + PUTS(@"**********************************************"); + PUTS(@""); +} diff --git a/tikzit/src/osx/AppDelegate.h b/tikzit/src/osx/AppDelegate.h new file mode 100644 index 0000000..349b47b --- /dev/null +++ b/tikzit/src/osx/AppDelegate.h @@ -0,0 +1,53 @@ +// +// AppDelegate.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 <Cocoa/Cocoa.h> +#import "StylePaletteController.h" +#import "ToolPaletteController.h" +#import "PropertyInspectorController.h" +#import "PreambleController.h" +#import "PreviewController.h" +#import "GraphicsView.h" + +@interface AppDelegate : NSObject { + NSMapTable *table; + StylePaletteController *stylePaletteController; + PropertyInspectorController *propertyInspectorController; + PreambleController *preambleController; + PreviewController *previewController; + ToolPaletteController *toolPaletteController; + IBOutlet GraphicsView *graphicsView; + NSString *tempDir; +} + +@property IBOutlet StylePaletteController *stylePaletteController; +@property IBOutlet ToolPaletteController *toolPaletteController; + +- (void)awakeFromNib; +- (void)applicationWillTerminate:(NSNotification *)notification; +- (IBAction)toggleStyleInspector:(id)sender; +- (IBAction)togglePropertyInspector:(id)sender; +- (IBAction)togglePreamble:(id)sender; +- (IBAction)refreshShapes:(id)sender; + +@end diff --git a/tikzit/src/osx/AppDelegate.m b/tikzit/src/osx/AppDelegate.m new file mode 100644 index 0000000..edfaf19 --- /dev/null +++ b/tikzit/src/osx/AppDelegate.m @@ -0,0 +1,105 @@ +// +// AppDelegate.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 "AppDelegate.h" +#import "TikzGraphAssembler.h" +#import "TikzDocument.h" +#import "Shape.h" +#import "SupportDir.h" + +@implementation AppDelegate + +@synthesize stylePaletteController, toolPaletteController; + + +- (void)awakeFromNib { + [TikzGraphAssembler setup]; // initialise lex/yacc parser globals + + [SupportDir createUserSupportDir]; + NSString *supportDir = [SupportDir userSupportDir]; + //NSLog(stylePlist); + stylePaletteController = + [[StylePaletteController alloc] initWithWindowNibName:@"StylePalette" + supportDir:supportDir]; + + propertyInspectorController = + [[PropertyInspectorController alloc] initWithWindowNibName:@"PropertyInspector"]; + + [propertyInspectorController setStylePaletteController:stylePaletteController]; + + NSString *preamblePlist = [supportDir stringByAppendingPathComponent:@"preambles.plist"]; + preambleController = + [[PreambleController alloc] initWithWindowNibName:@"Preamble" + plist:preamblePlist + styles:[stylePaletteController nodeStyles]]; + + char template[] = "/tmp/tikzit_tmp_XXXXXXX"; + char *dir = mkdtemp(template); + tempDir = [NSString stringWithUTF8String:dir]; + + NSLog(@"created temp dir: %@", tempDir); + NSLog(@"system support dir: %@", [SupportDir systemSupportDir]); + + previewController = + [[PreviewController alloc] initWithWindowNibName:@"Preview" + preambleController:preambleController + tempDir:tempDir]; + + // each application has one global preview controller + [PreviewController setDefaultPreviewController:previewController]; +} + +- (void)applicationWillTerminate:(NSNotification *)notification { + NSString *supportDir = [SupportDir userSupportDir]; + [stylePaletteController saveStyles:supportDir]; + [preambleController savePreambles:[supportDir stringByAppendingPathComponent:@"preambles.plist"]]; + + NSLog(@"wiping temp dir: %@", tempDir); + [[NSFileManager defaultManager] removeItemAtPath:tempDir error:NULL]; +} + +- (void)toggleController:(NSWindowController*)c { + if ([[c window] isVisible]) { + [c close]; + } else { + [c showWindow:self]; + } +} + +- (IBAction)toggleStyleInspector:(id)sender { + [self toggleController:stylePaletteController]; +} + +- (IBAction)togglePropertyInspector:(id)sender { + [self toggleController:propertyInspectorController]; +} + +- (IBAction)togglePreamble:(id)sender { + [self toggleController:preambleController]; +} + +- (IBAction)refreshShapes:(id)sender { + [Shape refreshShapeDictionary]; +} + +@end diff --git a/tikzit/src/osx/CALayer+DrawLabel.h b/tikzit/src/osx/CALayer+DrawLabel.h new file mode 100644 index 0000000..32282d9 --- /dev/null +++ b/tikzit/src/osx/CALayer+DrawLabel.h @@ -0,0 +1,21 @@ +// +// CALayer+DrawLabel.h +// TikZiT +// +// Created by Aleks Kissinger on 09/05/2011. +// Copyright 2011 Aleks Kissinger. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> + +@class Transformer; + +@interface CALayer(DrawLabel) + +- (void)drawLabel:(NSString*)label + atPoint:(NSPoint)pt + inContext:(CGContextRef)context + usingTrans:(Transformer*)t; + +@end diff --git a/tikzit/src/osx/CALayer+DrawLabel.m b/tikzit/src/osx/CALayer+DrawLabel.m new file mode 100644 index 0000000..4860a3c --- /dev/null +++ b/tikzit/src/osx/CALayer+DrawLabel.m @@ -0,0 +1,84 @@ +// +// CALayer+DrawLabel.m +// TikZiT +// +// Created by Aleks Kissinger on 09/05/2011. +// Copyright 2011 Aleks Kissinger. All rights reserved. +// + +#import "CALayer+DrawLabel.h" +#import "Transformer.h" + +@implementation CALayer(DrawLabel) + +- (void)drawLabel:(NSString*)label + atPoint:(NSPoint)pt + inContext:(CGContextRef)context + usingTrans:(Transformer*)t { + + CGContextSaveGState(context); + + if ([label length] > 15) { + label = [[label substringToIndex:12] stringByAppendingString:@"..."]; + } + + float fontSize = [t scaleToScreen:0.18f]; // size 9 @ 100% + if (fontSize > 18.0f) fontSize = 18.0f; + + // Prepare font + CTFontRef font = CTFontCreateWithName(CFSTR("Monaco"), fontSize, NULL); + + // Create an attributed string + CFStringRef keys[] = { kCTFontAttributeName }; + CFTypeRef values[] = { font }; + CFDictionaryRef attr = CFDictionaryCreate(NULL, + (const void **)&keys, + (const void **)&values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFAttributedStringRef attrString = + CFAttributedStringCreate(NULL, (CFStringRef)label, attr); + CFRelease(attr); + + // Draw the string + CTLineRef line = CTLineCreateWithAttributedString(attrString); + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + CGContextSetTextPosition(context, 0, 0); + + CGRect labelBounds = CGRectIntegral(CTLineGetImageBounds(line, context)); + //int shiftx = round(labelBounds.size.width / 2); + + CGContextSetTextPosition(context, + round(pt.x - (labelBounds.size.width/2)), + round(pt.y - (0.9*labelBounds.size.height/2))); + + labelBounds = CGRectIntegral(CTLineGetImageBounds(line, context)); + labelBounds.origin.x -= 2; + labelBounds.origin.y -= 2; + labelBounds.size.width += 4; + labelBounds.size.height += 4; + + CGContextSetShouldAntialias(context, NO); + + CGContextSetRGBFillColor(context, 1.0f, 1.0f, 0.5f, 0.7f); + CGContextSetRGBStrokeColor(context, 0.5f, 0.0f, 0.0f, 0.7f); + + CGContextFillRect(context, labelBounds); + CGContextStrokeRect(context, labelBounds); + + CGContextSetShouldAntialias(context, YES); + + CGContextSetRGBFillColor(context, 0.3f, 0.3f, 0.3f, 0.7f); + + CTLineDraw(line, context); + + // Clean up + CFRelease(line); + CFRelease(attrString); + CFRelease(font); + + CGContextRestoreGState(context); +} + +@end diff --git a/tikzit/src/osx/CoreGraphicsRenderContext.h b/tikzit/src/osx/CoreGraphicsRenderContext.h new file mode 100644 index 0000000..7b00484 --- /dev/null +++ b/tikzit/src/osx/CoreGraphicsRenderContext.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Cocoa/Cocoa.h> +#import "RenderContext.h" + +@interface CoreTextLayout: NSObject<TextLayout> { + CFAttributedStringRef attrString; + CTFontRef font; + CTLineRef line; + CGContextRef ctx; +} + ++ (CoreTextLayout*) layoutForContext:(CGContextRef)c withString:(NSString*)string fontSize:(CGFloat)fontSize; +- (id) initWithContext:(CGContextRef)cr withString:(NSString*)string fontSize:(CGFloat)fontSize; + +@end + +@interface CoreGraphicsRenderContext: NSObject<RenderContext> { + CGContextRef ctx; +} + ++ (CoreGraphicsRenderContext*) contextWithCGContext:(CGContextRef)c; ++ (id) initWithCGContext:(CGContextRef)c; + +- (CGContextRef) ctx; + +@end + +// vim:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit/src/osx/CoreGraphicsRenderContext.m b/tikzit/src/osx/CoreGraphicsRenderContext.m new file mode 100644 index 0000000..1cb0daf --- /dev/null +++ b/tikzit/src/osx/CoreGraphicsRenderContext.m @@ -0,0 +1,234 @@ +/* + * 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 <Cocoa/Cocoa.h> +#import "RenderContext.h" + +@implementation CoreTextLayout + ++ (CoreTextLayout*) layoutForContext:(CGContextRef)c withString:(NSString*)string fontSize:(CGFloat)fontSize { + return [[[self alloc] initWithContext:c withString:string fontSize:fontSize] autorelease]; +} + +- (id) initWithContext:(CGContextRef)cr withString:(NSString*)string fontSize:(CGFloat)fontSize { + self = [super init]; + + if (self == nil) { + return nil; + } + + CGContextRetain (cr); + ctx = cr; + font = CTFontCreateWithName(CFSTR("Monaco"), fontSize, NULL); + + // Create an attributed string + CFStringRef keys[] = { kCTFontAttributeName }; + CFTypeRef values[] = { font }; + CFDictionaryRef attr = CFDictionaryCreate(NULL, + (const void **)&keys, + (const void **)&values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + attrString = CFAttributedStringCreate(NULL, (CFStringRef)lab, attr); + CFRelease(attr); + line = CTLineCreateWithAttributedString(attrString); + + return self; +} + +- (NSSize) size { + CGRect labelBounds = CGRectIntegral(CTLineGetImageBounds(line, ctx)); + return labelBounds.size; +} + +- (NSString*) text { + return CFAttributedStringGetString (attrString); +} + +- (void) showTextAt:(NSPoint)topLeft withColor:(RColor)color { + CGContextSaveGState(ctx); + + CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextSetRGBStrokeColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextSetShouldAntialias(ctx, YES); + + CGContextSetTextMatrix(ctx, CGAffineTransformIdentity); + CGContextSetTextPosition(ctx, 0, 0); + CGRect bounds = CGRectIntegral(CTLineGetImageBounds(line, ctx)); + CGContextSetTextPosition(ctx, topLeft.x - bounds.x, topLeft.y - bounds.y); + + CTLineDraw(line, ctx); + + CGContextRestoreGState(ctx); +} + +- (void) dealloc { + CFRelease(line); + CFRelease(attrString); + CFRelease(font); + CGContextRelease (ctx); + + [super dealloc]; +} + +@end + +@implementation CoreGraphicsRenderContext + ++ (CoreGraphicsRenderContext*) contextWithCGContext:(CGContextRef)c { + return [[[self alloc] initWithCGContext:c] autorelease]; +} + ++ (id) initWithCGContext:(CGContextRef)c { + self = [super init]; + + if (self) { + ctx = c; + CGContextRetain (ctx); + } + + return self; +} + +- (void) dealloc { + CGContextRelease (ctx); + + [super dealloc]; +} + +- (CGContextRef) ctx { + return ctx; +} + +- (void) saveState { + CGContextSaveGState(ctx); +} + +- (void) restoreState { + CGContextRestoreGState(ctx); +} + +- (NSRect) clipBoundingBox { + return CGContextGetClipBoundingBox (ctx); +} + +- (BOOL) strokeIncludesPoint:(NSPoint)p { + return CGContextPathContainsPoint(ctx, NSPointToCGPoint(p), kCGPathStroke); +} + +- (BOOL) fillIncludesPoint:(NSPoint)p { + return CGContextPathContainsPoint(ctx, NSPointToCGPoint(p), kCGPathFill); +} + +- (id<TextLayout>) layoutText:(NSString*)text withSize:(CGFloat)fontSize { + return [CoreTextLayout layoutForContext:ctx withString:text fontSize:fontSize]; +} + +// this may not affect text rendering +- (void) setAntialiasMode:(AntialiasMode)mode { + CGContextSetShouldAntialias(ctx, mode != AntialiasDisabled); +} + +- (void) setLineWidth:(CGFloat)width { + CGContextSetLineWidth(ctx, width); +} + +// setting to 0 will unset the dash +- (void) setLineDash:(CGFloat)dashLength { + if (dashLength <= 0.0f) { + CGContextSetLineDash(ctx, 0.0f, NULL, 0); + } else { + const CGFloat dash[] = {dashLength, dashLength}; + CGContextSetLineDash(ctx, 0.0f, dash, 2); + } +} + +// paths +- (void) startPath { + CGContextBeginPath (ctx); +} + +- (void) closeSubPath { + CGContextClosePath (ctx); +} + +- (void) moveTo:(NSPoint)p { + CGContextMoveToPoint (ctx, p.x, p.y); +} + +- (void) curveTo:(NSPoint)end withCp1:(NSPoint)cp1 andCp2:(NSPoint)cp2 { + CGContextAddCurveToPoint(ctx, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y); +} + +- (void) lineTo:(NSPoint)end { + CGContextAddLineToPoint(ctx, end.x, end.y); +} + +- (void) rect:(NSRect)rect { + CGContextAddRect (ctx, rect); +} + +- (void) circleAt:(NSPoint)c withRadius:(CGFloat)r { + CGContextMoveToPoint (ctx, c.x + r, c.y); + CGContextAddArc (ctx, c.x, c.y, r, 0.0f, M_PI, 1); +} + +// these methods clear the path +- (void) strokePathWithColor:(RColor)color { + CGContextSetRGBStrokeColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextDrawPath (ctx, kCGPathStroke); +} + +- (void) fillPathWithColor:(RColor)color { + CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextDrawPath (ctx, kCGPathFill); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor { + CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextSetRGBStrokeColor(ctx, color.red, color.green, color.blue, color.alpha); + CGContextDrawPath (ctx, kCGPathFillStroke); +} + +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor + usingAlpha:(CGFloat)alpha { + CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha * alpha); + CGContextSetRGBStrokeColor(ctx, color.red, color.green, color.blue, color.alpha * alpha); + CGContextDrawPath (ctx, kCGPathFillStroke); +} + +- (void) clipToPath { + CGContextClip (ctx); +} + +// paint everywhere within the clip +- (void) paintWithColor:(RColor)color { + CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha); + CGRect r = CGContextGetClipBoundingBox (ctx); + r.origin.x -= 1; + r.origin.y -= 1; + r.size.width += 2; + r.size.height += 2; + CGContextFillRect(context, r); +} + +@end + +// vim:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit/src/osx/EdgeControlLayer.h b/tikzit/src/osx/EdgeControlLayer.h new file mode 100644 index 0000000..4cdf8bc --- /dev/null +++ b/tikzit/src/osx/EdgeControlLayer.h @@ -0,0 +1,44 @@ +// +// EdgeControlLayer.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 <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> +#import "Edge.h" +#import "Transformer.h" + + +@interface EdgeControlLayer : CALayer { + Edge *edge; + Transformer *transformer; + BOOL selected; +} + +- (id)initWithEdge:(Edge*)e andTransformer:(Transformer*)t; +- (void)highlight; +- (void)unhighlight; +- (void)select; +- (void)deselect; + ++ (float)handleRadius; + +@end diff --git a/tikzit/src/osx/EdgeControlLayer.m b/tikzit/src/osx/EdgeControlLayer.m new file mode 100644 index 0000000..377cde4 --- /dev/null +++ b/tikzit/src/osx/EdgeControlLayer.m @@ -0,0 +1,150 @@ +// +// EdgeControlLayer.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 "EdgeControlLayer.h" +#import "util.h" + + +@implementation EdgeControlLayer + + +- (id)initWithEdge:(Edge*)e andTransformer:(Transformer*)t { + [super init]; + transformer = t; + edge = e; + self.opacity = 0.0f; + return self; +} + +- (void)select { + selected = YES; + self.opacity = 1.0f; +} + +- (void)deselect { + selected = NO; + self.opacity = 0.0f; +} + +- (void)highlight { + if (!selected) { + self.opacity = 0.5f; + } +} + +- (void)unhighlight { + if (!selected) { + self.opacity = 0.0f; + } +} + +- (void)drawInContext:(CGContextRef)ctx { + CGContextSaveGState(ctx); + + [edge updateControls]; + CGPoint source = NSPointToCGPoint([transformer toScreen:[[edge source] point]]); + CGPoint target = NSPointToCGPoint([transformer toScreen:[[edge target] point]]); + CGPoint mid = NSPointToCGPoint([transformer toScreen:[edge mid]]); + CGPoint cp1 = NSPointToCGPoint([transformer toScreen:[edge cp1]]); + CGPoint cp2 = NSPointToCGPoint([transformer toScreen:[edge cp2]]); + + float dx = (target.x - source.x); + float dy = (target.y - source.y); + + // draw a circle at the midpoint + CGRect mid_rect = CGRectMake(mid.x-3.0f, mid.y-3.0f, 6.0f, 6.0f); + CGContextAddEllipseInRect(ctx, mid_rect); + CGContextSetLineWidth(ctx, 1.0f); + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, 0.5f); + CGContextSetRGBStrokeColor(ctx, 0.0f, 0.0f, 1.0f, 0.5f); + CGContextDrawPath(ctx, kCGPathFillStroke); + + + CGContextSetShouldAntialias(ctx, YES); + + // compute size of control circles + float cdist; + if (dx == 0 && dy == 0) cdist = [transformer scaleToScreen:edge.weight]; + else cdist = sqrt(dx*dx + dy*dy) * edge.weight; + + // if basic bend, draw blue, if inout, draw green + if ([edge bendMode] == EdgeBendModeBasic) CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 0.4f); + else CGContextSetRGBStrokeColor(ctx, 0, 0.7f, 0, 0.4f); + + // draw source control circle + CGRect ellipse1 = CGRectMake(source.x-cdist, source.y-cdist, cdist*2.0f, cdist*2.0f); + CGContextAddEllipseInRect(ctx, ellipse1); + if (dx!=0 || dy!=0) { + CGRect ellipse2 = CGRectMake(target.x-cdist, target.y-cdist, cdist*2.0f, cdist*2.0f); + CGContextAddEllipseInRect(ctx, ellipse2); + } + + CGContextStrokePath(ctx); + + float handleRad = [EdgeControlLayer handleRadius]; + + // handles + CGRect ctrl1 = CGRectMake(cp1.x-handleRad, cp1.y-handleRad, 2*handleRad, 2*handleRad); + CGRect ctrl2 = CGRectMake(cp2.x-handleRad, cp2.y-handleRad, 2*handleRad, 2*handleRad); + + CGContextSetRGBFillColor(ctx, 1.0f, 1.0f, 1.0f, 0.8f); + + // draw a line from source vertex to first handle + if ([edge bendMode] == EdgeBendModeInOut) { + if ([edge outAngle] % 45 == 0) CGContextSetRGBStrokeColor(ctx, 1, 0, 1, 0.6f); + else CGContextSetRGBStrokeColor(ctx, 0, 0.7f, 0, 0.4f); + } else { + if ([edge bend] % 45 == 0) CGContextSetRGBStrokeColor(ctx, 1, 0, 1, 0.6f); + else CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 0.4f); + } + + CGContextMoveToPoint(ctx, source.x, source.y); + CGContextAddLineToPoint(ctx, cp1.x, cp1.y); + CGContextStrokePath(ctx); + + CGContextAddEllipseInRect(ctx, ctrl1); + CGContextDrawPath(ctx, kCGPathFillStroke); + + + // draw a line from target vertex to second handle + if ([edge bendMode] == EdgeBendModeInOut) { + if ([edge inAngle] % 45 == 0) CGContextSetRGBStrokeColor(ctx, 1, 0, 1, 0.6f); + else CGContextSetRGBStrokeColor(ctx, 0, 0.7f, 0, 0.4f); + } else { + if ([edge bend] % 45 == 0) CGContextSetRGBStrokeColor(ctx, 1, 0, 1, 0.6f); + else CGContextSetRGBStrokeColor(ctx, 0, 0, 1, 0.4f); + } + + CGContextMoveToPoint(ctx, target.x, target.y); + CGContextAddLineToPoint(ctx, cp2.x, cp2.y); + CGContextStrokePath(ctx); + + CGContextAddEllipseInRect(ctx, ctrl2); + CGContextDrawPath(ctx, kCGPathFillStroke); + + CGContextRestoreGState(ctx); +} + ++ (float)handleRadius { return 4.0f; } + +@end diff --git a/tikzit/src/osx/EdgeStyle+Coder.h b/tikzit/src/osx/EdgeStyle+Coder.h new file mode 100644 index 0000000..e35c18f --- /dev/null +++ b/tikzit/src/osx/EdgeStyle+Coder.h @@ -0,0 +1,30 @@ +// +// EdgeStyle+Coder.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 <Cocoa/Cocoa.h> +#import "EdgeStyle.h" + +@interface EdgeStyle (Coder) +- (id)initWithCoder:(NSCoder*)coder; +- (void)encodeWithCoder:(NSCoder*)coder; +@end diff --git a/tikzit/src/osx/EdgeStyle+Coder.m b/tikzit/src/osx/EdgeStyle+Coder.m new file mode 100644 index 0000000..208fac0 --- /dev/null +++ b/tikzit/src/osx/EdgeStyle+Coder.m @@ -0,0 +1,50 @@ +// +// EdgeStyle+Coder.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+Coder.h" + +@implementation EdgeStyle (Coder) + +- (id)initWithCoder:(NSCoder*)coder { + [super init]; + + name = [coder decodeObjectForKey:@"name"]; + category = [coder decodeObjectForKey:@"category"]; + headStyle = [coder decodeIntForKey:@"headStyle"]; + tailStyle = [coder decodeIntForKey:@"tailStyle"]; + decorationStyle = [coder decodeIntForKey:@"decorationStyle"]; + thickness = [coder decodeFloatForKey:@"thickness"]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeObject:name forKey:@"name"]; + [coder encodeObject:category forKey:@"category"]; + [coder encodeInt:headStyle forKey:@"headStyle"]; + [coder encodeInt:tailStyle forKey:@"tailStyle"]; + [coder encodeInt:decorationStyle forKey:@"decorationStyle"]; + [coder encodeFloat:thickness forKey:@"thickness"]; +} + +@end diff --git a/tikzit/src/osx/Graph+Coder.h b/tikzit/src/osx/Graph+Coder.h new file mode 100644 index 0000000..1404fc2 --- /dev/null +++ b/tikzit/src/osx/Graph+Coder.h @@ -0,0 +1,17 @@ +// +// Graph+Coder.h +// TikZiT +// +// Created by Aleks Kissinger on 27/04/2010. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import <Foundation/Foundation.h> +#import "Graph.h" + +@interface Graph (Coder) + +- (id)initWithCoder:(NSCoder*)coder; +- (void)encodeWithCoder:(NSCoder*)coder; + +@end diff --git a/tikzit/src/osx/Graph+Coder.m b/tikzit/src/osx/Graph+Coder.m new file mode 100644 index 0000000..6a3f650 --- /dev/null +++ b/tikzit/src/osx/Graph+Coder.m @@ -0,0 +1,25 @@ +// +// Graph+Coder.m +// TikZiT +// +// Created by Aleks Kissinger on 27/04/2010. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import "Graph+Coder.h" +#import "TikzGraphAssembler.h" + +@implementation Graph(Coder) + +- (id)initWithCoder:(NSCoder*)coder { + NSString *tikz = [coder decodeObject]; + TikzGraphAssembler *ass = [[TikzGraphAssembler alloc] init]; + [ass parseTikz:tikz forGraph:self]; + return self; +} + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeObject:[self tikz]]; +} + +@end diff --git a/tikzit/src/osx/GraphicsView.h b/tikzit/src/osx/GraphicsView.h new file mode 100644 index 0000000..ddd005f --- /dev/null +++ b/tikzit/src/osx/GraphicsView.h @@ -0,0 +1,128 @@ +// +// GraphicsView.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 <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> +#import "PickSupport.h" +#import "Grid.h" +#import "Transformer.h" +#import "Graph.h" +#import "NodeStyle.h" +#import "StylePaletteController.h" +#import "ToolPaletteController.h" +#import "SelectBoxLayer.h" + +// mouse modes, corresponding to different tools. format: (tool)[sub-mode]Mode +typedef enum { + SelectMode = 0x10, + SelectBoxMode = 0x11, + SelectMoveMode = 0x12, + SelectEdgeBendMode = 0x14, + + NodeMode = 0x20, + + EdgeMode = 0x40, + EdgeDragMode = 0x41, + + CropMode = 0x80, + CropDragMode = 0x81 +} MouseMode; + +@class TikzSourceController; + +@interface GraphicsView : NSView { + BOOL enabled; + + IBOutlet NSApplication *application; + StylePaletteController *stylePaletteController; + ToolPaletteController *toolPaletteController; + + BOOL frameMoveMode; + + Graph *graph; + NSString *graphTikzOnMouseDown; + PickSupport *pickSupport; + //NSMapTable *nodeSelectionLayers; + NSMapTable *edgeControlLayers; + NSMapTable *nodeLayers; + NSPoint dragOrigin; + NSPoint dragTarget; + NSPoint oldTransformerOrigin; + NSPoint oldMainOrigin; + NSRect oldBounds; + //NSRect selectionBox; + Transformer *transformer; + + CALayer *mainLayer; + CALayer *gridLayer; + CALayer *graphLayer; + CALayer *hudLayer; + SelectBoxLayer *selectionLayer; + + MouseMode mouseMode; + Node *leaderNode; + Grid *grid; + + Edge *modifyEdge; + BOOL firstControlPoint; + + int bboxLeftRight; + int bboxBottomTop; + + NSUndoManager *documentUndoManager; + NSPoint startPoint; + + TikzSourceController *tikzSourceController; +} + +@property BOOL enabled; +@property Graph *graph; +@property IBOutlet TikzSourceController *tikzSourceController; +@property (readonly) Transformer *transformer; +@property (readonly) PickSupport *pickSupport; + +- (void)setDocumentUndoManager:(NSUndoManager*)um; +- (void)applyStyleToSelectedNodes:(NodeStyle*)style; +- (void)applyStyleToSelectedEdges:(EdgeStyle*)style; + +- (void)updateMouseMode; +- (void)refreshLayers; + +//- (void)registerUndo:(GraphChange *)change withActionName:(NSString*)name; +- (void)registerUndo:(NSString*)oldTikz withActionName:(NSString*)name; +//- (void)undoGraphChange:(GraphChange *)change; +- (void)undoGraphChange:(NSString*)oldTikz; +- (void)postGraphChange; +- (void)postSelectionChange; + +- (void)deselectAll:(id)sender; +- (void)selectAll:(id)sender; +- (void)cut:(id)sender; +- (void)copy:(id)sender; +- (void)paste:(id)sender; +- (void)delete:(id)sender; +- (void)flipHorizonal:(id)sender; +- (void)flipVertical:(id)sender; +- (void)reverseEdgeDirection:(id)sender; + +@end diff --git a/tikzit/src/osx/GraphicsView.m b/tikzit/src/osx/GraphicsView.m new file mode 100644 index 0000000..38ed1f0 --- /dev/null +++ b/tikzit/src/osx/GraphicsView.m @@ -0,0 +1,1142 @@ +// +// GraphicsView.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 "GraphicsView.h" +#import "util.h" +#import "CALayer+DrawLabel.h" + +#import "NodeSelectionLayer.h" +#import "NodeLayer.h" +#import "EdgeControlLayer.h" +#import "AppDelegate.h" +#import "TikzGraphAssembler.h" +#import "TikzSourceController.h" + +@interface GraphicsView (Private) +- (void)setupLayers; +- (void)addNodeLayers:(Node*)n; +- (void)addEdgeLayers:(Edge*)e; +- (void)removeNodeLayers:(Node*)n; +- (void)resetMainOrigin; +- (void)setMainOrigin:(NSPoint)o; +@end + +static CGColorRef cgGrayColor, cgWhiteColor, cgClearColor = nil; + + +@implementation GraphicsView + +@synthesize enabled, transformer, pickSupport, tikzSourceController; + +- (void)postGraphChange { + [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChanged" + object:self]; + [self postSelectionChange]; +} + +- (void)postSelectionChange { + [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectionChanged" + object:self]; +} + +- (id)initWithFrame:(NSRect)frame { + self = [super initWithFrame:frame]; + if (self) { + if (cgClearColor == nil) { + cgClearColor = CGColorGetConstantColor(kCGColorClear); + cgGrayColor = CGColorCreateGenericGray(0.5f, 0.5f); + cgWhiteColor = CGColorCreateGenericRGB(1, 1, 1, 1); + } + + transformer = [[Transformer alloc] init]; + mouseMode = SelectMode; + grid = [Grid gridWithSpacing:1.0f + subdivisions:4 + transformer:transformer]; + [grid setSize:NSSizeFromCGSize([gridLayer bounds].size)]; + [transformer setScale:PIXELS_PER_UNIT]; + + [self setupLayers]; + + leaderNode = nil; + pickSupport = [[PickSupport alloc] init]; + frameMoveMode = NO; + + enabled = YES; + [self setGraph:[Graph graph]]; + } + return self; +} + +- (void)awakeFromNib { + AppDelegate *del = [application delegate]; + stylePaletteController = [del stylePaletteController]; + toolPaletteController = [del toolPaletteController]; + [self refreshLayers]; + [self postGraphChange]; +} + +- (void)setupLayers { + mainLayer = [CALayer layer]; + [mainLayer setBackgroundColor:cgWhiteColor]; + [mainLayer setFrame:CGRectIntegral(NSRectToCGRect([self bounds]))]; + [mainLayer setOpacity:1.0f]; + [self setLayer:mainLayer]; + [self resetMainOrigin]; + + gridLayer = [CALayer layer]; + [gridLayer setDelegate:grid]; + [gridLayer setOpacity:0.3f]; + [mainLayer addSublayer:gridLayer]; + + graphLayer = [CALayer layer]; + [graphLayer setDelegate:self]; + [mainLayer addSublayer:graphLayer]; + + hudLayer = [CALayer layer]; + [mainLayer addSublayer:hudLayer]; + + selectionLayer = [SelectBoxLayer layer]; + [mainLayer addSublayer:selectionLayer]; + + [transformer setOrigin:NSMakePoint(NSMidX([self bounds]),NSMidY([self bounds]))]; + oldBounds = [self bounds]; + [self refreshLayers]; +} + +// Lion resume feature +//- (void)encodeRestorableStateWithCoder:(NSCoder*)coder { +// NSLog(@"got encode request"); +//} +//- (void)restoreStateWithCoder:(NSCoder*)coder { +// NSLog(@"got decode request"); +//} + +- (void)registerUndo:(NSString*)oldTikz withActionName:(NSString*)nm { + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:oldTikz]; + [documentUndoManager setActionName:nm]; +} + +- (void)revertToTikz:(NSString*)tikz { + [tikzSourceController setTikz:tikz]; + [tikzSourceController tryParseTikz]; + [self refreshLayers]; + [self postGraphChange]; +} + + +- (void)undoGraphChange:(NSString*)oldTikz { + NSString *currentTikz = [graph tikz]; + [self revertToTikz:oldTikz]; + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoGraphChange:) + object:currentTikz]; +} + +- (void)setGraph:(Graph*)gr { + graph = gr; + + NSEnumerator *e; + CALayer *layer; + + e = [edgeControlLayers objectEnumerator]; + while (layer = [e nextObject]) [layer removeFromSuperlayer]; + edgeControlLayers = [NSMapTable mapTableWithStrongToStrongObjects]; + + + e = [nodeLayers objectEnumerator]; + while (layer = [e nextObject]) [layer removeFromSuperlayer]; + nodeLayers = [NSMapTable mapTableWithStrongToStrongObjects]; + + for (Node *n in [graph nodes]) { + [n attachStyleFromTable:[stylePaletteController nodeStyles]]; + [self addNodeLayers:n]; + } + + for (Edge *e in [graph edges]) { + [e setAttributesFromData]; + [e attachStyleFromTable:[stylePaletteController edgeStyles]]; + [self addEdgeLayers:e]; + } +} + +- (Graph*)graph { return graph; } + +- (void)setMainOrigin:(NSPoint)o { + o.x = round(o.x); + o.y = round(o.y); + CGRect rect = [mainLayer frame]; + rect.origin = NSPointToCGPoint(o); + [mainLayer setFrame:rect]; +} + +- (void)resetMainOrigin { + NSRect bds = [self bounds]; + bds.origin.x -= bds.size.width; + bds.origin.y -= bds.size.height; + bds.size.width *= 3; + bds.size.height *= 3; + [mainLayer setFrame:NSRectToCGRect([self bounds])]; +} + +- (void)refreshLayers { + [gridLayer setFrame:[mainLayer frame]]; + [graphLayer setFrame:[mainLayer frame]]; + [hudLayer setFrame:[mainLayer frame]]; + [selectionLayer setFrame:[mainLayer frame]]; + + if (enabled) { + [hudLayer setBackgroundColor:cgClearColor]; + } else { + [hudLayer setBackgroundColor:cgGrayColor]; + } + + [grid setSize:NSSizeFromCGSize([gridLayer bounds].size)]; + [gridLayer setNeedsDisplay]; + [graphLayer setNeedsDisplay]; + [hudLayer setNeedsDisplay]; + + NSEnumerator *e = [edgeControlLayers objectEnumerator]; + CALayer *layer; + while (layer = [e nextObject]) { + [layer setFrame:[graphLayer frame]]; + [layer setNeedsDisplay]; + } +} + + +- (void)viewDidEndLiveResize { + [super viewDidEndLiveResize]; + NSPoint o = [transformer origin]; + o.x += round(([self bounds].size.width - oldBounds.size.width)/2.0f); + o.y += round(([self bounds].size.height - oldBounds.size.height)/2.0f); + [transformer setOrigin:o]; + oldBounds = [self bounds]; + [self refreshLayers]; +} + +- (void)applyStyleToSelectedNodes:(NodeStyle*)style { + NSString *oldTikz = [graph tikz]; + + for (Node *n in [pickSupport selectedNodes]) { + [n setStyle:style]; + [[nodeLayers objectForKey:n] setNeedsDisplay]; + } + + [self registerUndo:oldTikz withActionName:@"Apply Style to Nodes"]; + [self refreshLayers]; + [self postGraphChange]; +} + +- (void)applyStyleToSelectedEdges:(EdgeStyle*)style { + NSString *oldTikz = [graph tikz]; + + for (Edge *e in [pickSupport selectedEdges]) { + [e setStyle:style]; + } + + [self registerUndo:oldTikz withActionName:@"Apply Style to Edges"]; + [self refreshLayers]; + [self postGraphChange]; +} + +- (void)addNodeLayers:(Node*)n { + // add a node to the graph + [graph addNode:n]; + + NSPoint pt = [transformer toScreen:[n point]]; + + // add a node layer + NodeLayer *nl = [[NodeLayer alloc] initWithNode:n transformer:transformer]; + [nl setCenter:pt]; + [nodeLayers setObject:nl forKey:n]; + [graphLayer addSublayer:nl]; + [nl setNeedsDisplay]; +} + +- (void)removeNodeLayers:(Node*)n { + [[nodeLayers objectForKey:n] removeFromSuperlayer]; + [nodeLayers removeObjectForKey:n]; +} + +- (void)addEdgeLayers:(Edge *)e { + [graph addEdge:e]; + EdgeControlLayer *ecl = [[EdgeControlLayer alloc] initWithEdge:e andTransformer:transformer]; + [edgeControlLayers setObject:ecl forKey:e]; + [ecl setFrame:CGRectMake(10, 10, 100, 100)]; + [hudLayer addSublayer:ecl]; + [ecl setNeedsDisplay]; +} + +- (void)removeEdgeLayers:(Edge*)e { + [[edgeControlLayers objectForKey:e] removeFromSuperlayer]; + [edgeControlLayers removeObjectForKey:e]; + [self refreshLayers]; +} + +- (BOOL)circleWithCenter:(NSPoint)center andRadius:(float)radius containsPoint:(NSPoint)p { + float dx = center.x - p.x; + float dy = center.y - p.y; + return (dx*dx + dy*dy) <= radius*radius; +} + +- (BOOL)node:(Node*)node containsPoint:(NSPoint)p { + NodeLayer *nl = [nodeLayers objectForKey:node]; + return [nl nodeContainsPoint:p]; +} + +- (BOOL)edge:(Edge*)edge containsPoint:(NSPoint)p { +// NSPoint center = [transformer toScreen:edge.mid]; +// float dx = center.x - p.x; +// float dy = center.y - p.y; +// float radius = 5.0f; // tolerence for clicks +// return (dx*dx + dy*dy) <= radius*radius; + + CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + + // Save the graphics state before doing the hit detection. + CGContextSaveGState(ctx); + + NSPoint src = [transformer toScreen:[[edge source] point]]; + NSPoint targ = [transformer toScreen:[[edge target] point]]; + NSPoint cp1 = [transformer toScreen:[edge cp1]]; + NSPoint cp2 = [transformer toScreen:[edge cp2]]; + + CGContextSetLineWidth(ctx, 8.0f); + + CGContextMoveToPoint(ctx, src.x, src.y); + CGContextAddCurveToPoint(ctx, cp1.x, cp1.y, cp2.x, cp2.y, targ.x, targ.y); + + BOOL containsPoint = CGContextPathContainsPoint(ctx, NSPointToCGPoint(p), kCGPathStroke); + + CGContextSetRGBStrokeColor(ctx, 0, 0, 0, 0); + + CGContextStrokePath(ctx); + //CGContextFlush(ctx); + CGContextRestoreGState(ctx); + + return containsPoint; +} + +- (void)shiftNodes:(NSSet*)set from:(NSPoint)source to:(NSPoint)dest { + float dx = dest.x - source.x; + float dy = dest.y - source.y; + + for (Node *n in set) { + NSPoint p = [transformer toScreen:[n point]]; + p = [grid snapScreenPoint:NSMakePoint(p.x+dx, p.y+dy)]; + [n setPoint:[transformer fromScreen:p]]; + } +} + + +- (void)mouseDown:(NSEvent*)theEvent { + if (!enabled) return; + + [self updateMouseMode]; + + dragOrigin = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + dragTarget = dragOrigin; + + graphTikzOnMouseDown = [graph tikz]; + + if ([theEvent modifierFlags] & NSCommandKeyMask) { + oldTransformerOrigin = [transformer origin]; + oldMainOrigin = [self frame].origin; + frameMoveMode = YES; + return; + } + + if (mouseMode == SelectMode) { + [selectionLayer setActive:YES]; + [selectionLayer setSelectBox:NSRectAroundPoints(dragOrigin, dragOrigin)]; + [selectionLayer setNeedsDisplay]; + + modifyEdge = nil; + NSPoint cp1, cp2; + for (Edge *e in [pickSupport selectedEdges]) { + cp1 = [transformer toScreen:[e cp1]]; + cp2 = [transformer toScreen:[e cp2]]; + if ([self circleWithCenter:cp1 + andRadius:[EdgeControlLayer handleRadius] + containsPoint:dragOrigin]) + { + mouseMode = SelectEdgeBendMode; + modifyEdge = e; + firstControlPoint = YES; + break; + } else if ([self circleWithCenter:cp2 + andRadius:[EdgeControlLayer handleRadius] + containsPoint:dragOrigin]) + { + mouseMode = SelectEdgeBendMode; + modifyEdge = e; + firstControlPoint = NO; + break; + } + } + + if (modifyEdge == nil) { // skip all the rest if we're modifying an edge + + leaderNode = nil; + + // in first pass, try to find a leader node, under the mouse + for (Node* n in [graph nodes]) { + if ([self node:n containsPoint:dragOrigin]) { + leaderNode = n; + [gridLayer setOpacity:1.0f]; + break; + } + } + + // if we found one, deselect the others (if appropriate) and go to move mode + if (leaderNode != nil) { + startPoint = [leaderNode point]; + + // if we select a node, we should always deselect all edges: + for (Edge *e in [graph edges]) [[edgeControlLayers objectForKey:e] deselect]; + [pickSupport deselectAllEdges]; + + BOOL shouldDeselect = + !([theEvent modifierFlags] & NSShiftKeyMask) + && ![pickSupport isNodeSelected:leaderNode]; + for (Node *n in [graph nodes]) { + if (n != leaderNode && shouldDeselect) { + [pickSupport deselectNode:n]; + [[[nodeLayers objectForKey:n] selection] deselect]; + } + } + + // ensure the leader node is actually selected + if (![pickSupport isNodeSelected:leaderNode]) { + [pickSupport selectNode:leaderNode]; + [[[nodeLayers objectForKey:leaderNode] selection] select]; + } + + + // put us in move mode + mouseMode = SelectMoveMode; + } else { + mouseMode = SelectBoxMode; + + // if we didn't select a node, start hunting for an edge to select + BOOL shouldDeselect = !([theEvent modifierFlags] & NSShiftKeyMask); + + if (shouldDeselect) { + [pickSupport deselectAllEdges]; + for (Edge *e in graph.edges) [[edgeControlLayers objectForKey:e] deselect]; + } + + for (Edge* e in [graph edges]) { + // find the first node under the pointer, select it, show its controls + // and deselect all others if shift isn't down + if ([self edge:e containsPoint:dragOrigin]) { + for (Node *n in [pickSupport selectedNodes]) [[[nodeLayers objectForKey:n] selection] deselect]; + + [pickSupport deselectAllNodes]; + [pickSupport selectEdge:e]; + [[edgeControlLayers objectForKey:e] select]; + break; + } + } // end for e in [graph edges] + } // end if leaderNode == nil + } // end if modifyEdge == nil + + } else if (mouseMode == NodeMode) { + // do nothing... + } else if (mouseMode == EdgeMode) { + for (Node *n in [graph nodes]) { + if ([self node:n containsPoint:dragOrigin]) { + [[[nodeLayers objectForKey:n] selection] highlight]; + } + } + mouseMode = EdgeDragMode; + } else if (mouseMode == CropMode) { + if ([graph hasBoundingBox]) { + float fudge = 3; + + NSRect bb = [graph boundingBox]; + NSPoint bl = [transformer toScreen:bb.origin]; + NSPoint tr = [transformer + toScreen:NSMakePoint(bb.origin.x+bb.size.width, + bb.origin.y+bb.size.height)]; + if (dragOrigin.x > bl.x-fudge && dragOrigin.x < tr.x+fudge && + dragOrigin.y > tr.y-fudge && dragOrigin.y < tr.y+fudge) + { + bboxBottomTop = 1; + } else if (dragOrigin.x > bl.x-fudge && dragOrigin.x < tr.x+fudge && + dragOrigin.y > bl.y-fudge && dragOrigin.y < bl.y+fudge) + { + bboxBottomTop = -1; + } else { + bboxBottomTop = 0; + } + + if (dragOrigin.y > bl.y-fudge && dragOrigin.y < tr.y+fudge && + dragOrigin.x > tr.x-fudge && dragOrigin.x < tr.x+fudge) + { + bboxLeftRight = 1; + } else if (dragOrigin.y > bl.y-fudge && dragOrigin.y < tr.y+fudge && + dragOrigin.x > bl.x-fudge && dragOrigin.x < bl.x+fudge) + { + bboxLeftRight = -1; + } else { + bboxLeftRight = 0; + } + + if (bboxBottomTop != 0 || bboxLeftRight != 0) { + mouseMode = CropDragMode; + } + } + } else { + printf("WARNING: MOUSE DOWN IN INVALID MODE.\n"); + } + + [self refreshLayers]; +} + +- (void)mouseDragged:(NSEvent *)theEvent { + if (!enabled) return; + dragTarget = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + if (frameMoveMode) { + NSPoint newTransOrigin, newMainOrigin; + NSPoint diff = NSMakePoint(dragTarget.x - dragOrigin.x, dragTarget.y - dragOrigin.y); + newTransOrigin.x = oldTransformerOrigin.x + diff.x; + newTransOrigin.y = oldTransformerOrigin.y + diff.y; + newMainOrigin.x = oldMainOrigin.x + diff.x; + newMainOrigin.y = oldMainOrigin.y + diff.y; + + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + [self setMainOrigin:newMainOrigin]; + [CATransaction commit]; + + [transformer setOrigin:newTransOrigin]; + return; + } + + if (mouseMode == SelectBoxMode) { + [selectionLayer setSelectBox:NSRectAroundPoints(dragOrigin, dragTarget)]; + [selectionLayer setNeedsDisplay]; + + for (Node* n in [graph nodes]) { + if (NSPointInRect([transformer toScreen:[n point]], [selectionLayer selectBox])) { + [[[nodeLayers objectForKey:n] selection] highlight]; + } else if (!([theEvent modifierFlags] & NSShiftKeyMask)) { + [[[nodeLayers objectForKey:n] selection] unhighlight]; + } + } + } else if (mouseMode == SelectMoveMode) { + if (leaderNode != nil) { + [self shiftNodes:[pickSupport selectedNodes] + from:[transformer toScreen:[leaderNode point]] + to:dragTarget]; + } else { + printf("WARNING: LEADER NODE SHOULD NOT BE NIL.\n"); + } + + [self refreshLayers]; + } else if (mouseMode == SelectEdgeBendMode) { + NSPoint src = [transformer toScreen:[[modifyEdge source] point]]; + NSPoint targ = [transformer toScreen:[[modifyEdge target] point]]; + float dx1 = targ.x - src.x; + float dy1 = targ.y - src.y; + float dx2, dy2; + if (firstControlPoint) { + dx2 = dragTarget.x - src.x; + dy2 = dragTarget.y - src.y; + } else { + dx2 = dragTarget.x - targ.x; + dy2 = dragTarget.y - targ.y; + } + float base_dist = sqrt(dx1*dx1 + dy1*dy1); + float handle_dist = sqrt(dx2*dx2 + dy2*dy2); + float wcourseness = 0.1f; + + if (![modifyEdge isSelfLoop]) { + if (base_dist != 0) { + [modifyEdge setWeight:roundToNearest(wcourseness, handle_dist/base_dist)]; + //round(handle_dist / (base_dist*wcourseness)) * wcourseness; + } else { + [modifyEdge setWeight: + roundToNearest(wcourseness, [transformer scaleFromScreen:handle_dist])]; + } + } + + + float control_angle = good_atan(dx2, dy2); + + int bcourseness = 15; + + if ([modifyEdge bendMode] == EdgeBendModeBasic) { + float bnd; + float base_angle = good_atan(dx1, dy1); + if (firstControlPoint) { + bnd = base_angle - control_angle; + } else { + bnd = control_angle - base_angle + pi; + if (bnd > pi) bnd -= 2*pi; + } + + [modifyEdge setBend:round(bnd * (180.0f / pi) * + (1.0f / (float)bcourseness)) * + bcourseness]; + } else { + int bnd = round(control_angle * (180.0f / pi) * + (1.0f / (float)bcourseness)) * + bcourseness; + if (firstControlPoint) { + if ([theEvent modifierFlags] & NSAlternateKeyMask) { + if ([modifyEdge isSelfLoop]) { + [modifyEdge setInAngle:[modifyEdge inAngle] + + (bnd - [modifyEdge outAngle])]; + } else { + [modifyEdge setInAngle:[modifyEdge inAngle] - + (bnd - [modifyEdge outAngle])]; + } + } + + [modifyEdge setOutAngle:bnd]; + } else { + if (theEvent.modifierFlags & NSAlternateKeyMask) { + if ([modifyEdge isSelfLoop]) { + [modifyEdge setOutAngle:[modifyEdge outAngle] + + (bnd - [modifyEdge inAngle])]; + } else { + [modifyEdge setOutAngle:[modifyEdge outAngle] - + (bnd - [modifyEdge inAngle])]; + } + } + + [modifyEdge setInAngle:bnd]; + } + } + + [self refreshLayers]; + } else if (mouseMode == NodeMode) { + // do nothing... + } else if (mouseMode == EdgeDragMode) { + for (Node *n in [graph nodes]) { + if ([self node:n containsPoint:dragOrigin] || + [self node:n containsPoint:dragTarget]) + { + [[[nodeLayers objectForKey:n] selection] highlight]; + } else { + [[[nodeLayers objectForKey:n] selection] unhighlight]; + } + } + + [self refreshLayers]; + } else if (mouseMode == CropMode || mouseMode == CropDragMode) { + NSPoint p1 = [transformer fromScreen:[grid snapScreenPoint:dragOrigin]]; + NSPoint p2 = [transformer fromScreen:[grid snapScreenPoint:dragTarget]]; + + NSRect bbox; + if (mouseMode == CropDragMode) { + bbox = [graph boundingBox]; + if (bboxBottomTop == -1) { + float dy = p2.y - bbox.origin.y; + bbox.origin.y += dy; + bbox.size.height -= dy; + } else if (bboxBottomTop == 1) { + float dy = p2.y - (bbox.origin.y + bbox.size.height); + bbox.size.height += dy; + } + + if (bboxLeftRight == -1) { + float dx = p2.x - bbox.origin.x; + bbox.origin.x += dx; + bbox.size.width -= dx; + } else if (bboxLeftRight == 1) { + float dx = p2.x - (bbox.origin.x + bbox.size.width); + bbox.size.width += dx; + } + } else { + bbox = NSRectAroundPoints(p1, p2); + } + + [graph setBoundingBox:bbox]; + [self postGraphChange]; + [self refreshLayers]; + } else { + printf("WARNING: MOUSE DRAGGED IN INVALID MODE.\n"); + } +} + +- (void)mouseUp:(NSEvent*)theEvent { + if (!enabled) return; + + if (frameMoveMode) { + [CATransaction begin]; + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + [self resetMainOrigin]; + [self refreshLayers]; + [CATransaction commit]; + frameMoveMode = NO; + return; + } + + dragTarget = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + if ((mouseMode & SelectMode) == SelectMode && [theEvent clickCount] == 2) { + for (Edge *e in [graph edges]) { + if ([self edge:e containsPoint:dragTarget]) { + if ([e bendMode] == EdgeBendModeBasic) { + [e convertBendToAngles]; + [e setBendMode:EdgeBendModeInOut]; + } else { + [e convertAnglesToBend]; + [e setBendMode:EdgeBendModeBasic]; + } + + [self registerUndo:graphTikzOnMouseDown withActionName:@"Change Edge Mode"]; + [self postGraphChange]; + break; + } + } + } + + if (mouseMode == SelectBoxMode) { + for (Node* n in [graph nodes]) { + if (NSPointInRect([transformer toScreen:[n point]], [selectionLayer selectBox])) { + [pickSupport selectNode:n]; + [[[nodeLayers objectForKey:n] selection] select]; + } else if (!([theEvent modifierFlags] & NSShiftKeyMask)) { + [pickSupport deselectNode:n]; + [[[nodeLayers objectForKey:n] selection] deselect]; + } + } + + [selectionLayer setActive:NO]; + [selectionLayer setNeedsDisplay]; + [self postSelectionChange]; + + mouseMode = SelectMode; + } else if (mouseMode == SelectMoveMode) { + [gridLayer setOpacity:0.3f]; + + if (dragTarget.x != dragOrigin.x || dragTarget.y != dragOrigin.y) { + [self registerUndo:graphTikzOnMouseDown withActionName:@"Shift Nodes"]; + } + + leaderNode = nil; + + [self postGraphChange]; + mouseMode = SelectMode; + } else if (mouseMode == SelectEdgeBendMode) { + [self registerUndo:graphTikzOnMouseDown withActionName:@"Adjust Edge"]; + [self postGraphChange]; + mouseMode = SelectMode; + modifyEdge = nil; + } else if (mouseMode == NodeMode) { + NSPoint coords = [transformer fromScreen:[grid snapScreenPoint:dragTarget]]; + Node *n = [Node nodeWithPoint:coords]; + [n setStyle:[stylePaletteController activeNodeStyle]]; + [graph addNode:n]; + + [self registerUndo:graphTikzOnMouseDown withActionName:@"Add Node"]; + + [self addNodeLayers:n]; + [self postGraphChange]; + } else if (mouseMode == EdgeDragMode) { + Node *src = nil; + Node *targ = nil; + BOOL found = NO; // don't break the loop until everything is unhighlighted + for (Node *n in [graph nodes]) { + [[[nodeLayers objectForKey:n] selection] unhighlight]; + if (!found) { + if ([self node:n containsPoint:dragOrigin]) src = n; + if ([self node:n containsPoint:dragTarget]) targ = n; + if (src != nil && targ != nil) { + Edge *e = [Edge edgeWithSource:src andTarget:targ]; + [e setStyle:[stylePaletteController activeEdgeStyle]]; + [graph addEdge:e]; + [self registerUndo:graphTikzOnMouseDown withActionName:@"Add Edge"]; + [self addEdgeLayers:e]; + found = YES; + } + } + } + + [self postGraphChange]; + mouseMode = EdgeMode; + } else if (mouseMode == CropMode || mouseMode == CropDragMode) { + if (dragOrigin.x == dragTarget.x && dragOrigin.y == dragTarget.y) { + [graph setBoundingBox:NSMakeRect(0, 0, 0, 0)]; + [self registerUndo:graphTikzOnMouseDown withActionName:@"Clear Bounding Box"]; + [self postGraphChange]; + } else { + [self registerUndo:graphTikzOnMouseDown withActionName:@"Change Bounding Box"]; + } + + mouseMode = CropMode; + } else { + if (! ([theEvent modifierFlags] & NSCommandKeyMask)) + printf("WARNING: MOUSE UP IN INVALID MODE.\n"); + } + + [self refreshLayers]; +} + +- (void)drawNode:(Node*)nd onLayer:(CALayer*)layer inContext:(CGContextRef)context { + NSPoint pt = [transformer toScreen:[nd point]]; + + NodeLayer *nl = [nodeLayers objectForKey:nd]; + //[nl setStrokeWidth:2.0f]; + [nl setCenter:pt andAnimateWhen:(mouseMode != SelectMoveMode)]; +} + +- (void)drawEdge:(Edge*)e onLayer:(CALayer*)layer inContext:(CGContextRef)context { + CGContextSaveGState(context); + NSPoint src = [transformer toScreen:[[e source] point]]; + NSPoint targ = [transformer toScreen:[[e target] point]]; + NSPoint cp1 = [transformer toScreen:[e cp1]]; + NSPoint cp2 = [transformer toScreen:[e cp2]]; + + // all nodes have the same radius. this will need to be fixed + float sradius = 0;//(slayer.ghost) ? 0 : slayer.radius; + float tradius = 0;//(tlayer.ghost) ? 0 : tlayer.radius; + + float sdx = cp1.x - src.x; + float sdy = cp1.y - src.y; + float sdist = sqrt(sdx*sdx + sdy*sdy); + float sshortx = (sdist==0) ? 0 : sdx/sdist * sradius; + float sshorty = (sdist==0) ? 0 : sdy/sdist * sradius; + + float tdx = cp2.x - targ.x; + float tdy = cp2.y - targ.y; + float tdist = sqrt(tdx*tdx + tdy*tdy); + float tshortx = (tdist==0) ? 0 : tdx/sdist * tradius; + float tshorty = (tdist==0) ? 0 : tdy/sdist * tradius; + + CGContextMoveToPoint(context, src.x+sshortx, src.y+sshorty); + CGContextAddCurveToPoint(context, cp1.x, cp1.y, cp2.x, cp2.y, targ.x+tshortx, targ.y+tshorty); + + if ([e style] != nil) { + NSPoint p1,p2,p3; + + // draw edge decoration + switch ([[e style] decorationStyle]) { + case ED_None: + break; + case ED_Tick: + p1 = [transformer toScreen:[e leftNormal]]; + p2 = [transformer toScreen:[e rightNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + break; + case ED_Arrow: + p1 = [transformer toScreen:[e leftNormal]]; + p2 = [transformer toScreen:[e midTan]]; + p3 = [transformer toScreen:[e rightNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + CGContextAddLineToPoint(context, p3.x, p3.y); + break; + } + } + + + // OLD DRAW TICK CODE: + // float dx = targ.x - src.x; + // float dy = targ.y - src.y; + // float dist = sqrt(dx*dx+dy*dy); + // if (dist!=0) { + // CGContextMoveToPoint(context, + // round(mid.x + 4.0f * dy/dist), + // round(mid.y - 4.0f * dx/dist)); + // CGContextAddLineToPoint(context, + // round(mid.x - 4.0f * dy/dist), + // round(mid.y + 4.0f * dx/dist)); + // } + + + float lineWidth = [transformer scaleToScreen:0.04f]; + + CGContextSetLineWidth(context, lineWidth); + CGContextSetRGBStrokeColor(context, 0, 0, 0, 1); + CGContextStrokePath(context); + + CGContextRestoreGState(context); + + if ([e hasEdgeNode]) { + Node *en = [e edgeNode]; + NSPoint mid = [transformer toScreen:[e mid]]; + if (![[en label] isEqual:@""]) { + [layer drawLabel:[en label] + atPoint:mid + inContext:context + usingTrans:transformer]; + } + } + + EdgeControlLayer *ecl = [edgeControlLayers objectForKey:e]; + [ecl setNeedsDisplay]; +} + + +// draw the graph layer +-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { + for (Edge* e in [graph edges]) [self drawEdge:e onLayer:layer inContext:context]; + + for (Node* n in [graph nodes]) [self drawNode:n onLayer:layer inContext:context]; + + if ([graph hasBoundingBox]) { + CGRect bbox = NSRectToCGRect(NSIntegralRect( + [transformer rectToScreen:[graph boundingBox]])); + CGContextSetRGBStrokeColor(context, 1.0f, 0.7f, 0.5f, 1.0f); + CGContextSetLineWidth(context, 1.0f); + CGContextSetShouldAntialias(context, NO); + CGContextStrokeRect(context, bbox); + CGContextSetShouldAntialias(context, YES); + } + + if (mouseMode == EdgeDragMode) { + CGContextMoveToPoint(context, dragOrigin.x, dragOrigin.y); + CGContextAddLineToPoint(context, dragTarget.x, dragTarget.y); + CGContextSetLineWidth(context, 2); + CGContextSetRGBStrokeColor(context, 0, 0, 1, 1); + CGContextStrokePath(context); + } +} + +// if enabled, suppress the default "bonk" behaviour on key presses +- (void)keyDown:(NSEvent *)theEvent { + if (!enabled) [super keyDown:theEvent]; +} + +- (void)delete:(id)sender { + BOOL didDelete = NO; + NSString *oldTikz = [graph tikz]; + + if ([[pickSupport selectedNodes] count] != 0) { + GraphChange *change = [graph removeNodes:[pickSupport selectedNodes]]; + for (Node *n in [change affectedNodes]) [self removeNodeLayers:n]; + for (Edge *e in [change affectedEdges]) [self removeEdgeLayers:e]; + + [self refreshLayers]; + [self postGraphChange]; + didDelete = YES; + } + + if ([[pickSupport selectedEdges] count] != 0) { + [graph removeEdges:[pickSupport selectedEdges]]; + for (Edge *e in [pickSupport selectedEdges]) [self removeEdgeLayers:e]; + [self refreshLayers]; + [self postGraphChange]; + didDelete = YES; + } + + [pickSupport deselectAllNodes]; + [pickSupport deselectAllEdges]; + + if (didDelete) [self registerUndo:oldTikz withActionName:@"Delete Nodes or Edges"]; +} + +- (void)keyUp:(NSEvent *)theEvent { + if (!enabled) return; + + id sender = self; + switch ([theEvent keyCode]) { + case 51: // delete + [self delete:sender]; // "self" is the sender + break; + case 1: // S + [toolPaletteController setSelectedTool:TikzToolSelect]; + break; + case 45: // N + case 9: // V + [toolPaletteController setSelectedTool:TikzToolNode]; + break; + case 14: // E + [toolPaletteController setSelectedTool:TikzToolEdge]; + //[self updateMouseMode]; + break; + case 40: // K + [toolPaletteController setSelectedTool:TikzToolCrop]; + break; + } + [self refreshLayers]; +} + + +- (void)deselectAll:(id)sender { + [pickSupport deselectAllNodes]; + [pickSupport deselectAllEdges]; + + for (Node *n in [graph nodes]) { + [[[nodeLayers objectForKey:n] selection] deselect]; + } + + for (Edge *e in [graph edges]) { + [[edgeControlLayers objectForKey:e] deselect]; + } + + [self postSelectionChange]; +} + +- (void)selectAll:(id)sender { + [pickSupport selectAllNodes:graph.nodes]; + + for (Node *n in [graph nodes]) { + [[[nodeLayers objectForKey:n] selection] select]; + } + + [self postSelectionChange]; +} + + +- (void)updateMouseMode { + switch (toolPaletteController.selectedTool) { + case TikzToolSelect: + mouseMode = SelectMode; + break; + case TikzToolNode: + mouseMode = NodeMode; + break; + case TikzToolEdge: + mouseMode = EdgeMode; + break; + case TikzToolCrop: + mouseMode = CropMode; + break; + } +} + +- (void)setDocumentUndoManager:(NSUndoManager *)um { + documentUndoManager = um; +} + +- (void)copy:(id)sender { + if ([pickSupport selectedNodes].count != 0) { + Graph *clip = [graph copyOfSubgraphWithNodes:[pickSupport selectedNodes]]; + NSString *tikz = [clip tikz]; + NSData *data = [tikz dataUsingEncoding:NSUTF8StringEncoding]; + //NSLog(@"about to copy: %@", tikz); + NSPasteboard *cb = [NSPasteboard generalPasteboard]; + [cb declareTypes:[NSArray arrayWithObject:@"tikzpicture"] owner:self]; + [cb setData:data forType:@"tikzpicture"]; + } +} + +- (void)cut:(id)sender { + if ([[pickSupport selectedNodes] count] != 0) { + [self copy:sender]; + [self delete:sender]; + + // otherwise, menu will say "Undo Delete Graph" + [documentUndoManager setActionName:@"Cut Graph"]; + } +} + +- (void)paste:(id)sender { + NSPasteboard *cb = [NSPasteboard generalPasteboard]; + NSString *type = [cb availableTypeFromArray:[NSArray arrayWithObject:@"tikzpicture"]]; + if (type) { + NSData *data = [cb dataForType:type]; + NSString *tikz = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + //NSLog(@"pasting tikz:\n%@",tikz); + TikzGraphAssembler *ass = [[TikzGraphAssembler alloc] init]; + if ([ass parseTikz:tikz]) { + //NSLog(@"tikz pasted:\n%@",tikz); + Graph *clip = [ass graph]; + + NSRect graphBounds = [graph bounds]; + NSRect clipBounds = [clip bounds]; + float dx = graphBounds.origin.x + + graphBounds.size.width - + clipBounds.origin.x + 0.5f; + [clip shiftNodes:[clip nodes] byPoint:NSMakePoint(dx, 0)]; + + if ([[clip nodes] count] != 0) { + NSString *oldTikz = [graph tikz]; + [self deselectAll:self]; + + // select everything from the clipboard + for (Node *n in [clip nodes]) { + [n attachStyleFromTable:[stylePaletteController nodeStyles]]; + [self addNodeLayers:n]; + [pickSupport selectNode:n]; + [[[nodeLayers objectForKey:n] selection] select]; + } + + for (Edge *e in [clip edges]) { + [e attachStyleFromTable:[stylePaletteController edgeStyles]]; + [self addEdgeLayers:e]; + } + + [graph insertGraph:clip]; + + [self registerUndo:oldTikz withActionName:@"Paste Graph"]; + [self refreshLayers]; + [self postGraphChange]; + } + } else { + NSLog(@"Error: couldn't parse tikz picture from clipboard."); + } + + } +} + +- (void)flipHorizonal:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph flipHorizontalNodes:[pickSupport selectedNodes]]; + [self registerUndo:oldTikz withActionName:@"Flip Horizontal"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (void)flipVertical:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph flipVerticalNodes:[pickSupport selectedNodes]]; + [self registerUndo:oldTikz withActionName:@"Flip Vertical"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (void)reverseEdgeDirection:(id)sender { + NSString *oldTikz = [graph tikz]; + + NSSet *es; + if ([[pickSupport selectedEdges] count] != 0) { + es = [pickSupport selectedEdges]; + } else { + es = [graph incidentEdgesForNodes:[pickSupport selectedNodes]]; + } + + for (Edge *e in es) [e reverse]; + + [self registerUndo:oldTikz withActionName:@"Flip Edge Direction"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (BOOL)acceptsFirstResponder { return YES; } +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { return YES; } +- (BOOL)canBecomeKeyView { return YES; } + + +@end diff --git a/tikzit/src/osx/Grid.h b/tikzit/src/osx/Grid.h new file mode 100644 index 0000000..76826e2 --- /dev/null +++ b/tikzit/src/osx/Grid.h @@ -0,0 +1,48 @@ +// +// Grid.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 <Cocoa/Cocoa.h> +#import "Transformer.h" + +@interface Grid : NSObject { + float gridX, gridY; + //float gridCellX, gridCellY; + int subdivisions; + Transformer *transformer; + NSSize size; +} + +@property NSSize size; + +- (id)initWithSpacing:(float)spacing subdivisions:(int)subs transformer:(Transformer*)t; ++ (Grid*)gridWithSpacing:(float)spacing subdivisions:(int)subs transformer:(Transformer*)t; +- (NSPoint)snapScreenPoint:(NSPoint)p; +- (float)gridX; +- (float)gridY; +- (int)subdivisions; +- (void)setSubdivisions:(int)subs; + +// Grid can also draw itself on a layer +- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; + +@end diff --git a/tikzit/src/osx/Grid.m b/tikzit/src/osx/Grid.m new file mode 100644 index 0000000..3e412a3 --- /dev/null +++ b/tikzit/src/osx/Grid.m @@ -0,0 +1,152 @@ +// +// Grid.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 "Grid.h" + + +@implementation Grid + +@synthesize size; + +- (id)initWithSpacing:(float)spacing + subdivisions:(int)subs + transformer:(Transformer*)t +{ + [super init]; + gridX = spacing; + gridY = spacing; + subdivisions = subs; + size.width = 0; + size.height = 0; + transformer = t; + return self; +} + ++ (Grid*)gridWithSpacing:(float)spacing + subdivisions:(int)subs + transformer:(Transformer*)t +{ + return [[Grid alloc] initWithSpacing:spacing + subdivisions:subs + transformer:t]; +} + +- (float)gridX { + return gridX; +} + +- (float)gridY { + return gridY; +} + +- (int)subdivisions { + return subdivisions; +} + +- (void)setSubdivisions:(int)subs { + subdivisions = subs; +} + +- (NSPoint)snapScreenPoint:(NSPoint)p { + NSPoint snap; + + float gridCellX = [transformer scaleToScreen:gridX] / (float)subdivisions; + float gridCellY = [transformer scaleToScreen:gridY] / (float)subdivisions; + + // snap along grid lines, relative to the origin + snap.x = floor(((p.x-[transformer origin].x)/gridCellX)+0.5)*gridCellX + [transformer origin].x; + snap.y = floor(((p.y-[transformer origin].y)/gridCellY)+0.5)*gridCellY + [transformer origin].y; + return snap; +} + +-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context +{ + CGContextSaveGState(context); + + CGContextSetShouldAntialias(context, NO); + + float x,y; + float grX = [transformer scaleToScreen:gridX]; + float grY = [transformer scaleToScreen:gridY]; + + float gridCellX = grX / (float)subdivisions; + float gridCellY = grY / (float)subdivisions; + + for (x = [transformer origin].x + gridCellX; x < size.width; x += gridCellX) { + CGContextMoveToPoint(context, x, 0); + CGContextAddLineToPoint(context, x, size.height); + } + + for (x = [transformer origin].x - gridCellX; x > 0; x -= gridCellX) { + CGContextMoveToPoint(context, x, 0); + CGContextAddLineToPoint(context, x, size.height); + } + + for (y = [transformer origin].y + gridCellY; y < size.height; y += gridCellY) { + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, size.width, y); + } + + for (y = [transformer origin].y - gridCellY; y > 0; y -= gridCellY) { + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, size.width, y); + } + + CGContextSetRGBStrokeColor(context, 0.9, 0.9, 1, 1); + CGContextStrokePath(context); + + for (x = [transformer origin].x + grX; x < size.width; x += grX) { + CGContextMoveToPoint(context, x, 0); + CGContextAddLineToPoint(context, x, size.height); + } + + for (x = [transformer origin].x - grX; x > 0; x -= grX) { + CGContextMoveToPoint(context, x, 0); + CGContextAddLineToPoint(context, x, size.height); + } + + for (y = [transformer origin].y + grY; y < size.height; y += grY) { + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, size.width, y); + } + + for (y = [transformer origin].y + grY; y > 0; y -= grY) { + CGContextMoveToPoint(context, 0, y); + CGContextAddLineToPoint(context, size.width, y); + } + + CGContextSetRGBStrokeColor(context, 0.8, 0.8, 0.9, 1); + CGContextStrokePath(context); + + CGContextMoveToPoint(context, [transformer origin].x, 0); + CGContextAddLineToPoint(context, [transformer origin].x, size.height); + CGContextMoveToPoint(context, 0, [transformer origin].y); + CGContextAddLineToPoint(context, size.width, [transformer origin].y); + + CGContextSetRGBStrokeColor(context, 0.6, 0.6, 0.7, 1); + CGContextStrokePath(context); + + CGContextRestoreGState(context); +} + +@end diff --git a/tikzit/src/osx/MultiCombo.h b/tikzit/src/osx/MultiCombo.h new file mode 100644 index 0000000..c8ec769 --- /dev/null +++ b/tikzit/src/osx/MultiCombo.h @@ -0,0 +1,18 @@ +// +// MultiCombo.h +// TikZiT +// +// Created by Aleks Kissinger on 21/04/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + + +@interface MultiCombo : NSComboBox { + BOOL multi; +} + +@property (readwrite,assign) BOOL multi; + +@end diff --git a/tikzit/src/osx/MultiCombo.m b/tikzit/src/osx/MultiCombo.m new file mode 100644 index 0000000..8930460 --- /dev/null +++ b/tikzit/src/osx/MultiCombo.m @@ -0,0 +1,38 @@ +// +// MultiCombo.m +// TikZiT +// +// Created by Aleks Kissinger on 21/04/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "MultiCombo.h" + + +@implementation MultiCombo + +- (void)textDidChange:(NSNotification *)notification { + [super textDidChange:notification]; + [self setMulti:NO]; +} + +- (void)setMulti:(BOOL)m { + multi = m; + if (multi) { + [self setTextColor:[NSColor grayColor]]; + [self setStringValue:@"multiple values"]; + } +} + +- (BOOL)multi { return multi; } + +- (BOOL)becomeFirstResponder { + [super becomeFirstResponder]; + if ([self multi]) { + [self setTextColor:[NSColor blackColor]]; + [self setStringValue:@""]; + } + return YES; +} + +@end diff --git a/tikzit/src/osx/MultiField.h b/tikzit/src/osx/MultiField.h new file mode 100644 index 0000000..39eeefa --- /dev/null +++ b/tikzit/src/osx/MultiField.h @@ -0,0 +1,18 @@ +// +// LabelField.h +// TikZiT +// +// Created by Aleks Kissinger on 20/04/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + + +@interface MultiField : NSTextField { + BOOL multi; +} + +@property (readwrite,assign) BOOL multi; + +@end diff --git a/tikzit/src/osx/MultiField.m b/tikzit/src/osx/MultiField.m new file mode 100644 index 0000000..7c5aac3 --- /dev/null +++ b/tikzit/src/osx/MultiField.m @@ -0,0 +1,53 @@ +// +// LabelField.m +// TikZiT +// +// Created by Aleks Kissinger on 20/04/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "MultiField.h" + + +@implementation MultiField + +- (void)textDidChange:(NSNotification *)notification { + [super textDidChange:notification]; + [self setMulti:NO]; +} + +- (void)setMulti:(BOOL)m { + multi = m; + if (multi) { + [self setTextColor:[NSColor grayColor]]; + [self setStringValue:@"multiple values"]; + } +} + +- (BOOL)multi { return multi; } + +- (BOOL)becomeFirstResponder { + [super becomeFirstResponder]; + if ([self multi]) { + [self setTextColor:[NSColor blackColor]]; + [self setStringValue:@""]; + } + return YES; +} + +//- (BOOL)textShouldBeginEditing:(NSText *)textObject { +// [super textShouldBeginEditing:textObject]; +// NSLog(@"about to type"); +// return YES; +//} + +//- (void)textDidEndEditing:(NSNotification *)obj { +// [super textDidEndEditing:obj]; +// +// NSLog(@"focus lost"); +// if ([self multi]) { +// [self setMulti:YES]; +// } +//} + +@end diff --git a/tikzit/src/osx/NilToEmptyStringTransformer.h b/tikzit/src/osx/NilToEmptyStringTransformer.h new file mode 100644 index 0000000..1445a94 --- /dev/null +++ b/tikzit/src/osx/NilToEmptyStringTransformer.h @@ -0,0 +1,28 @@ +// +// NilToEmptyStringTransformer.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 NilToEmptyStringTransformer : NSValueTransformer + +@end diff --git a/tikzit/src/osx/NilToEmptyStringTransformer.m b/tikzit/src/osx/NilToEmptyStringTransformer.m new file mode 100644 index 0000000..97267e3 --- /dev/null +++ b/tikzit/src/osx/NilToEmptyStringTransformer.m @@ -0,0 +1,53 @@ +// +// NilToEmptyStringTransformer.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 "NilToEmptyStringTransformer.h" + +@implementation NilToEmptyStringTransformer + +- (id)init { + [super init]; + return self; +} + ++ (Class)transformedValueClass { + return [NSString class]; +} + ++ (BOOL)allowsReverseTransformation { + return YES; +} + +- (id)transformedValue:(id)value { + if (value == nil) { + return @""; + } else { + return value; + } +} + +- (id)reverseTransformedValue:(id)value { + return [self transformedValue:value]; +} + +@end diff --git a/tikzit/src/osx/NodeLayer.h b/tikzit/src/osx/NodeLayer.h new file mode 100644 index 0000000..dfe05e8 --- /dev/null +++ b/tikzit/src/osx/NodeLayer.h @@ -0,0 +1,62 @@ +// +// NodeLayer.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 <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> +#import "Transformer.h" +#import "Shape.h" +#import "Node.h" +#import "NodeStyle+Coder.h" +#import "NodeSelectionLayer.h" + +@interface NodeLayer : CALayer { + Node *node; + Shape *shape; + CGMutablePathRef path; + float textwidth; + NSPoint center; + Transformer *transformer; + Transformer *localTrans; + NodeSelectionLayer *selection; + BOOL rescale; + BOOL dirty; // need to rebuild CGBezierPath of the shape +} + +@property Node *node; +@property (assign) NSPoint center; +@property (assign) BOOL rescale; +@property (retain) NodeSelectionLayer *selection; +@property (readonly) CGMutablePathRef path; + +- (id)initWithNode:(Node*)n transformer:(Transformer*)t; +- (NSColor*)strokeColor; +- (NSColor*)fillColor; +- (float)strokeWidth; + +- (void)setCenter:(NSPoint)ctr andAnimateWhen:(BOOL)anim; +- (void)updateFrame; +- (BOOL)nodeContainsPoint:(NSPoint)p; + +- (void)drawInContext:(CGContextRef)context; + +@end diff --git a/tikzit/src/osx/NodeLayer.m b/tikzit/src/osx/NodeLayer.m new file mode 100644 index 0000000..da01bec --- /dev/null +++ b/tikzit/src/osx/NodeLayer.m @@ -0,0 +1,239 @@ +// +// NodeLayer.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 "NodeLayer.h" +#import "CALayer+DrawLabel.h" +#import "NSString+LatexConstants.h" +#import "Shape.h" +#import "ShapeNames.h" +#import "Node.h" +#import "Edge.h" + +@implementation NodeLayer + +@synthesize node, selection, rescale; + +- (id)initWithNode:(Node *)n transformer:(Transformer*)t { + [super init]; + node = n; + selection = [[NodeSelectionLayer alloc] init]; + [selection setNodeLayer:self]; + localTrans = [[Transformer alloc] init]; + + [self addSublayer:selection]; + textwidth = 0.0f; + center = NSMakePoint(0.0f, 0.0f); + transformer = t; + + path = NULL; + rescale = YES; + dirty = YES; + + [self updateFrame]; + return self; +} + +- (NSColor*)strokeColor { + if ([node style] != nil) { + return [[[node style] strokeColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + } else { + return nil; + } +} + +- (NSColor*)fillColor { + if ([node style] != nil) { + return [[[node style] fillColor] colorUsingColorSpace:[NSColorSpace deviceRGBColorSpace]]; + } else { + return nil; + } +} + +- (float)strokeWidth { + if ([node style] != nil) { + return [node.style strokeThickness]; + } else { + return 1; + } +} + +- (NSPoint)center { return center; } + +- (void)setCenter:(NSPoint)ctr { + center.x = round(ctr.x); + center.y = round(ctr.y); + [self updateFrame]; +} + +- (void)setCenter:(NSPoint)ctr andAnimateWhen:(BOOL)anim { + [CATransaction begin]; + if (!anim) { + [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; + } + [self setCenter:ctr]; + [CATransaction commit]; +} + +- (void)updateShape { + Shape *s = ([node style] != nil) ? + [Shape shapeForName:[[node style] shapeName]] : + [Shape shapeForName:SHAPE_CIRCLE]; + if (s != shape) { // straight pointer comparison + shape = s; + dirty = YES; + } +} + +- (void)updateLocalTrans { + float scale = ([node style] != nil) ? [[node style] scale] : 1.0f; + + Transformer *t = [Transformer transformer]; + float rad = ([transformer scaleToScreen:scale] / 2.0f) + 8.0f; + [t setOrigin:NSMakePoint(rad, rad)]; + [t setScale:[transformer scale]*((rescale)?scale:0.8f)]; + + if (![localTrans isEqual:t]) { + dirty = YES; + localTrans = t; + } +} + +- (void)updateFrame { + [self updateLocalTrans]; + [self updateShape]; + float rad = [localTrans origin].x; + [self setFrame:CGRectIntegral(CGRectMake(center.x - rad, center.y - rad, 2*rad, 2*rad))]; + NSRect bds = NSMakeRect(0, 0, 2*rad, 2*rad); + [selection setFrame:NSRectToCGRect(bds)]; + + [self setNeedsDisplay]; + [selection setNeedsDisplay]; +} + +- (CGMutablePathRef)path { + if (dirty) { + CGMutablePathRef pth = CGPathCreateMutable(); + NSPoint p, cp1, cp2; + for (NSArray *arr in [shape paths]) { + BOOL fst = YES; + for (Edge *e in arr) { + if (fst) { + fst = NO; + p = [localTrans toScreen:[[e source] point]]; + CGPathMoveToPoint(pth, nil, p.x, p.y); + } + + p = [localTrans toScreen:[[e target] point]]; + if ([e isStraight]) { + CGPathAddLineToPoint(pth, nil, p.x, p.y); + } else { + cp1 = [localTrans toScreen:[e cp1]]; + cp2 = [localTrans toScreen:[e cp2]]; + CGPathAddCurveToPoint(pth, nil, cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y); + } + } + + CGPathCloseSubpath(pth); + } + + if (path != NULL) CFRelease(path); + path = pth; + dirty = NO; + } + + + return path; +} + +- (BOOL)nodeContainsPoint:(NSPoint)p { + CGPoint p1 = CGPointMake(p.x - [self frame].origin.x, p.y - [self frame].origin.y); + return CGPathContainsPoint([self path],nil,p1,NO); +} + + +- (void)drawInContext:(CGContextRef)context { + CGContextSaveGState(context); + + + if ([node style] == nil) { + CGContextSetRGBStrokeColor(context, 0.4f, 0.4f, 0.7f, 1.0f); + CGContextSetRGBFillColor(context, 0.4f, 0.4f, 0.7f, 1.0f); + //CGRect fr = [self frame]; + CGRect bds = NSRectToCGRect([localTrans rectToScreen:NSMakeRect(-0.5, -0.5, 1, 1)]); + CGRect pt = CGRectMake(CGRectGetMidX(bds)-1.0f, CGRectGetMidY(bds)-1.0f, 2.0f, 2.0f); + CGContextSetLineWidth(context, 0); + CGContextAddEllipseInRect(context, pt); + CGContextFillPath(context); + + // HACK: for some reason, CGFloat isn't getting typedef'ed properly + +#ifdef __x86_64__ + const double dash[2] = {2.0,2.0}; +#else + const float dash[2] = {2.0,2.0}; +#endif + CGContextSetLineDash(context, 0.0, dash, 2); + CGContextSetLineWidth(context, 1); + CGContextAddPath(context, [self path]); + CGContextStrokePath(context); + } else { + NSColor *stroke = [self strokeColor]; + NSColor *fill = [self fillColor]; + + CGContextSetRGBStrokeColor(context, + [stroke redComponent], + [stroke greenComponent], + [stroke blueComponent], + [stroke alphaComponent]); + + CGContextSetLineWidth(context, [self strokeWidth]); + + CGContextSetRGBFillColor(context, + [fill redComponent], + [fill greenComponent], + [fill blueComponent], + [fill alphaComponent]); + + + CGContextSetLineWidth(context, [self strokeWidth]); + CGContextAddPath(context, [self path]); + CGContextDrawPath(context, kCGPathFillStroke); + } + + if (!([node label] == nil || [[node label] isEqual:@""])) { + NSPoint labelPt = NSMakePoint([self frame].size.width/2, [self frame].size.height/2); + [self drawLabel:[[node label] stringByExpandingLatexConstants] + atPoint:labelPt + inContext:context + usingTrans:transformer]; + } + + CGContextRestoreGState(context); +} + +- (void)dealloc { + if (path != NULL) CFRelease(path); + [super dealloc]; +} + +@end diff --git a/tikzit/src/osx/NodeSelectionLayer.h b/tikzit/src/osx/NodeSelectionLayer.h new file mode 100644 index 0000000..99ee75f --- /dev/null +++ b/tikzit/src/osx/NodeSelectionLayer.h @@ -0,0 +1,45 @@ +// +// NodeSelectionLayer.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 <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> +#import "Shape.h" + +@class NodeLayer; + +@interface NodeSelectionLayer : CALayer { + BOOL selected; + CGMutablePathRef path; + NSLock *drawLock; + NodeLayer *nodeLayer; +} + +@property NodeLayer *nodeLayer; + +- (id)init; +- (void)select; +- (void)deselect; +- (void)highlight; +- (void)unhighlight; + +@end diff --git a/tikzit/src/osx/NodeSelectionLayer.m b/tikzit/src/osx/NodeSelectionLayer.m new file mode 100644 index 0000000..5efcbf7 --- /dev/null +++ b/tikzit/src/osx/NodeSelectionLayer.m @@ -0,0 +1,93 @@ +// +// NodeSelectionLayer.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 "NodeSelectionLayer.h" +#import "NodeLayer.h" +#import "CircleShape.h" + +@implementation NodeSelectionLayer + +@synthesize nodeLayer; + +- (id)init { + [super init]; + selected = NO; + drawLock = [[NSLock alloc] init]; + nodeLayer = nil; + [self setOpacity:0.0f]; + return self; +} + + +- (void)select { + selected = YES; + [self setOpacity:0.5f]; +} + +- (void)deselect { + selected = NO; + [self setOpacity:0.0f]; +} + +- (void)highlight { + if (!selected) { + [self setOpacity:0.25f]; + } +} + +- (void)unhighlight { + if (!selected) { + [self setOpacity:0.0f]; + } +} + +//- (CGMutablePathRef)path { +// return path; +//} +// +//- (void)setPath:(CGMutablePathRef)p { +// path = CGPathCreateMutableCopy(p); +// CFMakeCollectable(path); +//} + +- (void)drawInContext:(CGContextRef)context { + [drawLock lock]; + CGContextSaveGState(context); + + //CGContextSetRGBStrokeColor(context, 0.61f, 0.735f, 1.0f, 1.0f); + CGContextSetRGBStrokeColor(context, 0.61f, 0.735f, 1.0f, 1.0f); + CGContextSetRGBFillColor(context, 0.61f, 0.735f, 1.0f, 1.0f); + CGContextSetLineWidth(context, 6.0f); + + if (nodeLayer != nil) { + CGContextAddPath(context, [nodeLayer path]); + } else { + NSLog(@"WARNING: attempting to draw selection with path = nil."); + } + CGContextDrawPath(context, kCGPathFillStroke); + + CGContextRestoreGState(context); + [drawLock unlock]; +} + +@end diff --git a/tikzit/src/osx/NodeStyle+Coder.h b/tikzit/src/osx/NodeStyle+Coder.h new file mode 100644 index 0000000..b6443af --- /dev/null +++ b/tikzit/src/osx/NodeStyle+Coder.h @@ -0,0 +1,36 @@ +// +// NodeStyle+Coder.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 <Cocoa/Cocoa.h> +#import "NodeStyle.h" + + +@interface NodeStyle(Coder) + +@property (copy) NSColor *fillColor; +@property (copy) NSColor *strokeColor; + +- (id)initWithCoder:(NSCoder *)coder; +- (void)encodeWithCoder:(NSCoder *)coder; + +@end diff --git a/tikzit/src/osx/NodeStyle+Coder.m b/tikzit/src/osx/NodeStyle+Coder.m new file mode 100644 index 0000000..8da91c1 --- /dev/null +++ b/tikzit/src/osx/NodeStyle+Coder.m @@ -0,0 +1,91 @@ +// +// NodeStyle+Coder.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+Coder.h" +#import "ShapeNames.h" + +@implementation NodeStyle(Coder) + +- (NSColor*)fillColor { + return [NSColor colorWithDeviceRed:fillColorRGB.redFloat + green:fillColorRGB.greenFloat + blue:fillColorRGB.blueFloat + alpha:1.0f]; +} + +- (void)setFillColor:(NSColor*)c { + NSColor *c1 = [c colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + [self willChangeValueForKey:@"fillColorIsKnown"]; + fillColorRGB = [ColorRGB colorWithFloatRed:c1.redComponent + green:c1.greenComponent + blue:c1.blueComponent]; + [self didChangeValueForKey:@"fillColorIsKnown"]; +} + +- (NSColor*)strokeColor { + return [NSColor colorWithDeviceRed:strokeColorRGB.redFloat + green:strokeColorRGB.greenFloat + blue:strokeColorRGB.blueFloat + alpha:1.0f]; +} + +- (void)setStrokeColor:(NSColor*)c { + NSColor *c1 = [c colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + [self willChangeValueForKey:@"strokeColorIsKnown"]; + strokeColorRGB = [ColorRGB colorWithFloatRed:c1.redComponent + green:c1.greenComponent + blue:c1.blueComponent]; + [self didChangeValueForKey:@"strokeColorIsKnown"]; +} + +- (id)initWithCoder:(NSCoder *)coder { + [super init]; + + // decode keys + name = [coder decodeObjectForKey:@"name"]; + [self setStrokeColor:[coder decodeObjectForKey:@"strokeColor"]]; + [self setFillColor:[coder decodeObjectForKey:@"fillColor"]]; + strokeThickness = [coder decodeIntForKey:@"strokeThickness"]; + shapeName = [coder decodeObjectForKey:@"shapeName"]; + category = [coder decodeObjectForKey:@"category"]; + scale = [coder decodeFloatForKey:@"scale"]; + + // apply defaults + if (scale == 0.0f) scale = 1.0f; + if (shapeName == nil) shapeName = SHAPE_CIRCLE; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:name forKey:@"name"]; + [coder encodeObject:[self strokeColor] forKey:@"strokeColor"]; + [coder encodeObject:[self fillColor] forKey:@"fillColor"]; + [coder encodeInt:strokeThickness forKey:@"strokeThickness"]; + [coder encodeObject:shapeName forKey:@"shapeName"]; + [coder encodeObject:category forKey:@"category"]; + [coder encodeFloat:scale forKey:@"scale"]; +} + + +@end diff --git a/tikzit/src/osx/PreambleController.h b/tikzit/src/osx/PreambleController.h new file mode 100644 index 0000000..4f7fb33 --- /dev/null +++ b/tikzit/src/osx/PreambleController.h @@ -0,0 +1,57 @@ +// +// PreambleController.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 <Cocoa/Cocoa.h> +#import "Preambles.h" +#import "Preambles+Coder.h" + +@interface PreambleController : NSWindowController { + Preambles *preambles; + IBOutlet NSTextView *textView; + IBOutlet NSToolbar *toolbar; + IBOutlet NSToolbarItem *defaultToolbarItem; + IBOutlet NSToolbarItem *customToolbarItem; + IBOutlet NSDictionaryController *preambleDictionaryController; + NSDictionary *textAttrs; + NSAttributedString *preambleText; + NSColor *ghostColor; + + NSIndexSet *selectionIndexes; +} + +@property (readonly) BOOL useDefaultPreamble; +@property (readonly) Preambles *preambles; +@property (retain) NSAttributedString *preambleText; +@property (retain) NSIndexSet *selectionIndexes; + +- (id)initWithWindowNibName:(NSString *)windowNibName plist:(NSString*)plist styles:(NSArray*)sty; +- (void)savePreambles:(NSString*)plist; +- (NSString*)currentPreamble; +- (NSString*)currentPostamble; +- (IBAction)setPreamble:(id)sender; +- (IBAction)insertDefaultStyles:(id)sender; + +- (IBAction)addPreamble:(id)sender; +- (IBAction)duplicatePreamble:(id)sender; + +@end diff --git a/tikzit/src/osx/PreambleController.m b/tikzit/src/osx/PreambleController.m new file mode 100644 index 0000000..ea95716 --- /dev/null +++ b/tikzit/src/osx/PreambleController.m @@ -0,0 +1,168 @@ +// +// PreambleController.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 "PreambleController.h" + + +@implementation PreambleController + +@synthesize preambleText, preambles; + +- (id)initWithWindowNibName:(NSString *)windowNibName plist:(NSString*)plist styles:(NSArray*)sty { + [super initWithWindowNibName:windowNibName]; + + preambles = (Preambles*)[NSKeyedUnarchiver unarchiveObjectWithFile:plist]; + [preambles setStyles:sty]; + if (preambles == nil) preambles = [[Preambles alloc] init]; + + preambleText = nil; + + NSFont *font = [NSFont userFixedPitchFontOfSize:11.0f]; + textAttrs = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; + ghostColor = [NSColor colorWithDeviceRed:0.9f green:0.9f blue:0.9f alpha:1.0f]; + + + + return self; +} + +- (void)awakeFromNib { + NSArray *arr = [preambleDictionaryController arrangedObjects]; + NSString *current = [preambles selectedPreambleName]; + + if (current != nil && ![current isEqual:@"default"]) { + for (int i = 0; i < [arr count]; ++i) { + if ([[[arr objectAtIndex:i] key] isEqual:current]) { + [self setSelectionIndexes:[NSIndexSet indexSetWithIndex:i]]; + break; + } + } + } +} + +- (BOOL)useDefaultPreamble { + return [[preambles selectedPreambleName] isEqualToString:@"default"]; +} + +- (void)flushText { + if (preambleText != nil && ![self useDefaultPreamble]) { + [preambles setCurrentPreamble:[preambleText string]]; + } +} + +- (void)setCurrentPreamble:(NSString*)current { + [self flushText]; + + [self willChangeValueForKey:@"useDefaultPreamble"]; + [preambles setSelectedPreambleName:current]; + [self didChangeValueForKey:@"useDefaultPreamble"]; + + [self setPreambleText: + [[NSAttributedString alloc] initWithString:[preambles currentPreamble] + attributes:textAttrs]]; +} + +- (void)showWindow:(id)sender { + [super showWindow:sender]; + if ([self useDefaultPreamble]) { + [toolbar setSelectedItemIdentifier:[defaultToolbarItem itemIdentifier]]; + } else { + [toolbar setSelectedItemIdentifier:[customToolbarItem itemIdentifier]]; + } + + [self setPreamble:self]; +} + +- (void)savePreambles:(NSString*)plist { + [self flushText]; + [NSKeyedArchiver archiveRootObject:preambles toFile:plist]; +} + +- (NSString*)currentPreamble { + [self flushText]; + return [preambles currentPreamble]; +} + +- (NSString*)currentPostamble { + return [preambles currentPostamble]; +} + +- (void)setSelectionIndexes:(NSIndexSet *)idx { + [self willChangeValueForKey:@"selectionIndexes"]; + selectionIndexes = idx; + [self didChangeValueForKey:@"selectionIndexes"]; + + [self setPreamble:self]; +} + +- (NSIndexSet*)selectionIndexes { + return selectionIndexes; +} + +- (IBAction)setPreamble:(id)sender { + if ([[toolbar selectedItemIdentifier] isEqualToString:[defaultToolbarItem itemIdentifier]]) { + [self setCurrentPreamble:@"default"]; + [textView setBackgroundColor:ghostColor]; + } else if ([toolbar.selectedItemIdentifier isEqualToString:[customToolbarItem itemIdentifier]]) { + NSString *key = nil; + if ([selectionIndexes count]==1) { + int i = [selectionIndexes firstIndex]; + key = [[[preambleDictionaryController arrangedObjects] objectAtIndex:i] key]; + } + if (key != nil) { + [self setCurrentPreamble:key]; + //NSLog(@"preamble set to %@", key); + } else { + [self setCurrentPreamble:@"default"]; + //NSLog(@"preamble set to default"); + } + [textView setBackgroundColor:[NSColor whiteColor]]; + } +} + +- (IBAction)insertDefaultStyles:(id)sender { + [textView insertText:[preambles styleDefinitions]]; +} + +- (IBAction)addPreamble:(id)sender { + [preambleDictionaryController setInitialKey:@"new preamble"]; + [preambleDictionaryController setInitialValue:[preambles defaultPreamble]]; + [preambleDictionaryController add:sender]; +} + +- (void)controlTextDidEndEditing:(NSNotification *)obj { + //NSLog(@"got a text change"); + [self setPreamble:[obj object]]; +} + + +// NOT IMPLEMENTED +- (IBAction)duplicatePreamble:(id)sender { +// NSLog(@"set text to: %@", [preambles currentPreamble]); +// [preambleDictionaryController setInitialKey:[preambles selectedPreambleName]]; +// [preambleDictionaryController setInitialValue:[preambles currentPreamble]]; +// [preambleDictionaryController add:sender]; +} + + +@end diff --git a/tikzit/src/osx/Preambles+Coder.h b/tikzit/src/osx/Preambles+Coder.h new file mode 100644 index 0000000..5a270c5 --- /dev/null +++ b/tikzit/src/osx/Preambles+Coder.h @@ -0,0 +1,32 @@ +// +// Preambles+Coder.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 "Preambles.h" +#import <Cocoa/Cocoa.h> + +@interface Preambles (Coder) + +- (id)initWithCoder:(NSCoder *)coder; +- (void)encodeWithCoder:(NSCoder *)coder; + +@end diff --git a/tikzit/src/osx/Preambles+Coder.m b/tikzit/src/osx/Preambles+Coder.m new file mode 100644 index 0000000..6b9768a --- /dev/null +++ b/tikzit/src/osx/Preambles+Coder.m @@ -0,0 +1,41 @@ +// +// Preambles+Coder.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+Coder.h" + + +@implementation Preambles (Coder) + +- (id)initWithCoder:(NSCoder *)coder { + [super init]; + selectedPreambleName = [coder decodeObjectForKey:@"selectedPreamble"]; + preambleDict = [coder decodeObjectForKey:@"preambles"]; + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeObject:selectedPreambleName forKey:@"selectedPreamble"]; + [coder encodeObject:preambleDict forKey:@"preambles"]; +} + +@end diff --git a/tikzit/src/osx/PreviewController.h b/tikzit/src/osx/PreviewController.h new file mode 100644 index 0000000..d6d855e --- /dev/null +++ b/tikzit/src/osx/PreviewController.h @@ -0,0 +1,51 @@ +// +// PreviewController.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 <Cocoa/Cocoa.h> + +@class PDFView; +@class PreambleController; + +@interface PreviewController : NSWindowController { + IBOutlet PDFView *pdfView; + IBOutlet NSProgressIndicator *progressIndicator; + IBOutlet NSScrollView *errorTextView; + IBOutlet NSTextView *errorText; + PreambleController *preambleController; + NSString *tempDir; + NSLock *latexLock; + int typesetCount; +} + + +- (id)initWithWindowNibName:(NSString*)nib + preambleController:(PreambleController*)pc + tempDir:(NSString*)dir; + +- (void)buildTikz:(NSString*)tikz; + ++ (void)setDefaultPreviewController:(PreviewController*)pc; ++ (PreviewController*)defaultPreviewController; + +@end diff --git a/tikzit/src/osx/PreviewController.m b/tikzit/src/osx/PreviewController.m new file mode 100644 index 0000000..e54a91f --- /dev/null +++ b/tikzit/src/osx/PreviewController.m @@ -0,0 +1,137 @@ +// +// PreviewController.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 "PreviewController.h" +#import "AppDelegate.h" +#import "PreambleController.h" +#import <Quartz/Quartz.h> + +@implementation PreviewController + +static PreviewController *preview = nil; + +- (id)initWithWindowNibName:(NSString*)nib + preambleController:(PreambleController*)pc + tempDir:(NSString*)dir { + [super initWithWindowNibName:nib]; + tempDir = [dir copy]; + typesetCount = 0; + preambleController = pc; + latexLock = [[NSLock alloc] init]; + return self; +} + +- (void)runLatex:(id)tikz { + // Only build one tex file at a time, so we don't get funky results. + [latexLock lock]; + [progressIndicator startAnimation:self]; + + int fnum = typesetCount++; + + NSString *tex = [NSString stringWithFormat:@"%@%@%@", + [preambleController currentPreamble], + tikz, + [preambleController currentPostamble]]; + + NSString *texFile = [NSString stringWithFormat:@"%@/tikzit_%d.tex", tempDir, fnum]; + NSString *pdfFile = [NSString stringWithFormat:@"%@/tikzit_%d.pdf", tempDir, fnum]; + + [tex writeToFile:texFile atomically:NO encoding:NSUTF8StringEncoding error:NULL]; + + // We run pdflatex in a bash shell to have easy access to the setup from unix-land + NSTask *latexTask = [[NSTask alloc] init]; + [latexTask setCurrentDirectoryPath:tempDir]; + [latexTask setLaunchPath:@"/bin/bash"]; + + // This assumes the user has $PATH set up to find pdflatex in either .profile + // or .bashrc. This should be improved to take other path setups into account + // and to be customisable. + NSString *latexCmd = + [NSString stringWithFormat: + @"if [ -e ~/.profile ]; then source ~/.profile; fi\n" + @"if [ -e ~/.bashrc ]; then source ~/.bashrc; fi\n" + @"pdflatex -interaction=nonstopmode -halt-on-error '%@'\n", + texFile]; + + NSPipe *pout = [NSPipe pipe]; + NSPipe *pin = [NSPipe pipe]; + [latexTask setStandardOutput:pout]; + [latexTask setStandardInput:pin]; + + NSFileHandle *latexIn = [pin fileHandleForWriting]; + NSFileHandle *latexOut = [pout fileHandleForReading]; + + [latexTask launch]; + [latexIn writeData:[latexCmd dataUsingEncoding:NSUTF8StringEncoding]]; + [latexIn closeFile]; + [latexTask waitUntilExit]; + + NSData *data = [latexOut readDataToEndOfFile]; + NSString *str = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + if ([latexTask terminationStatus] != 0) { + [errorTextView setHidden:YES]; + errorText.string = [@"\nAN ERROR HAS OCCURRED, PDFLATEX SAID:\n\n" stringByAppendingString:str]; + [errorTextView setHidden:NO]; + } else { + errorText.string = @""; + [errorTextView setHidden:YES]; + + data = [NSData dataWithContentsOfFile:pdfFile]; + PDFDocument *doc = [[PDFDocument alloc] initWithData:data]; + + // pad the PDF by a couple of pixels + if ([doc pageCount] >= 1) { + PDFPage *page = [doc pageAtIndex:0]; + NSRect box = [page boundsForBox:kPDFDisplayBoxCropBox]; + box.origin.x -= 2.0f; + box.origin.y -= 2.0f; + box.size.width += 4.0f; + box.size.height += 4.0f; + [page setBounds:box forBox:kPDFDisplayBoxCropBox]; + [page setBounds:box forBox:kPDFDisplayBoxMediaBox]; + } + + [pdfView setDocument:doc]; + } + + [progressIndicator stopAnimation:self]; + [latexLock unlock]; +} + +- (void)buildTikz:(NSString*)tikz { + // Build on a separate thread to keep the interface responsive. + [NSThread detachNewThreadSelector:@selector(runLatex:) toTarget:self withObject:tikz]; +} + ++ (void)setDefaultPreviewController:(PreviewController*)pc { + preview = pc; +} + ++ (PreviewController*)defaultPreviewController { + return preview; +} + + +@end diff --git a/tikzit/src/osx/PropertyInspectorController.h b/tikzit/src/osx/PropertyInspectorController.h new file mode 100644 index 0000000..cb14021 --- /dev/null +++ b/tikzit/src/osx/PropertyInspectorController.h @@ -0,0 +1,73 @@ +// +// PropertyInspectorController.h +// TikZiT +// +// Created by Aleks Kissinger on 17/07/2011. +// Copyright 2011 Aleks Kissinger. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import "NodeStyle.h" +#import "GraphElementData.h" + +@class SFBInspectorView; +@class StylePaletteController; + +@interface PropertyInspectorController : NSWindowController { + IBOutlet SFBInspectorView *propertyInspectorView; + IBOutlet NSView *nodePropertiesView; + IBOutlet NSView *graphPropertiesView; + IBOutlet NSView *edgePropertiesView; + IBOutlet NSTextField *edgeNodeLabelField; + IBOutlet NSButton *edgeNodeCheckbox; + IBOutlet NSArrayController *nodeDataArrayController; + IBOutlet NSArrayController *graphDataArrayController; + IBOutlet NSArrayController *edgeDataArrayController; + IBOutlet NSArrayController *edgeNodeDataArrayController; + + NSMutableArray *selectedNodes; + IBOutlet NSArrayController *selectedNodesArrayController; + + NSMutableArray *selectedEdges; + IBOutlet NSArrayController *selectedEdgesArrayController; + + // this data lists exists solely for displaying messages in disabled data tables + GraphElementData *noSelection; + GraphElementData *multipleSelection; + GraphElementData *noEdgeNode; + GraphElementData *noGraph; + + + // used to get access to the global style table + StylePaletteController *stylePaletteController; +} + +//@property (readonly) BOOL enableNodeDataControls; +//@property (readonly) BOOL enableEdgeDataControls; +@property (retain) NSMutableArray *selectedNodes; +@property (retain) NSMutableArray *selectedEdges; +@property (retain) StylePaletteController *stylePaletteController; + +- (id)initWithWindowNibName:(NSString *)windowNibName; +- (void)graphSelectionChanged:(NSNotification*)notification; + +- (IBAction)addNodeProperty:(id)sender; +- (IBAction)addNodeAtom:(id)sender; +- (IBAction)removeNodeProperty:(id)sender; + +- (IBAction)addGraphProperty:(id)sender; +- (IBAction)addGraphAtom:(id)sender; +- (IBAction)removeGraphProperty:(id)sender; + +- (IBAction)addEdgeProperty:(id)sender; +- (IBAction)addEdgeAtom:(id)sender; +- (IBAction)removeEdgeProperty:(id)sender; + +- (IBAction)addEdgeNodeProperty:(id)sender; +- (IBAction)addEdgeNodeAtom:(id)sender; +- (IBAction)removeEdgeNodeProperty:(id)sender; + +//- (IBAction)addRemoveChildNode:(id)sender; +- (IBAction)refreshDocument:(id)sender; + +@end diff --git a/tikzit/src/osx/PropertyInspectorController.m b/tikzit/src/osx/PropertyInspectorController.m new file mode 100644 index 0000000..8254949 --- /dev/null +++ b/tikzit/src/osx/PropertyInspectorController.m @@ -0,0 +1,272 @@ +// +// PropertyInspectorController.m +// TikZiT +// +// Created by Aleks Kissinger on 17/07/2011. +// Copyright 2011 Aleks Kissinger. All rights reserved. +// + +#import "PropertyInspectorController.h" +#import "StylePaletteController.h" +#import "TikzDocument.h" +#import "SFBInspectors/SFBInspectorView.h" +#import "PickSupport.h" +#import "Node.h" +#import "Edge.h" +#import "NodeStyle.h" +#import "GraphicsView.h" +#import "GraphElementProperty.h" +#import "Shape.h" + +@implementation PropertyInspectorController + +@synthesize stylePaletteController; +@synthesize selectedNodes, selectedEdges; + +- (id)initWithWindowNibName:(NSString *)windowNibName { + [super initWithWindowNibName:windowNibName]; + + noSelection = [[GraphElementData alloc] init]; + [noSelection setProperty:@"" forKey:@"No Selection"]; + multipleSelection = [[GraphElementData alloc] init]; + [multipleSelection setProperty:@"" forKey:@"Mult. Selection"]; + noEdgeNode = [[GraphElementData alloc] init]; + [noEdgeNode setProperty:@"" forKey:@"No Child"]; + noGraph = [[GraphElementData alloc] init]; + [noGraph setProperty:@"" forKey:@"No Graph"]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(graphSelectionChanged:) + name:@"SelectionChanged" + object:nil]; + +// [[NSDocumentController sharedDocumentController] addObserver:self +// forKeyPath:@"currentDocument" +// options:NSKeyValueObservingOptionNew +// context:NULL]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(graphSelectionChanged:) + name:@"NSWindowDidBecomeMainNotification" + object:nil]; + + + + + [[self window] setLevel:NSNormalWindowLevel]; + [self showWindow:self]; + return self; +} + +- (void)observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context { + [self graphSelectionChanged:nil]; +} + +//- (void)willChangeValueForKey:(NSString *)key { +// [super willChangeValueForKey:key]; +// NSLog(@"will: %@",key); +//} +// +//- (void)didChangeValueForKey:(NSString *)key { +// [super didChangeValueForKey:key]; +// NSLog(@"did: %@",key); +//} + +- (void)windowDidLoad { + [[self window] setMovableByWindowBackground:YES]; + + [propertyInspectorView addInspectorPane:graphPropertiesView + title:@"Graph Properties"]; + [propertyInspectorView addInspectorPane:nodePropertiesView + title:@"Node Properties"]; + [propertyInspectorView addInspectorPane:edgePropertiesView + title:@"Edge Properties"]; + [super windowDidLoad]; +} + +- (IBAction)refreshDocument:(id)sender { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + [[doc graphicsView] postGraphChange]; + [[doc graphicsView] refreshLayers]; + } +} + + +- (void)updateGraphFields { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + [graphDataArrayController setContent:[[[doc graphicsView] graph] data]]; + [graphDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [graphDataArrayController setEditable:YES]; + } else { + [graphDataArrayController setContent:noGraph]; + [graphDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [graphDataArrayController setEditable:NO]; + } +} + +- (void)updateNodeFields { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + if (doc != nil) { + NSSet *sel = [[[doc graphicsView] pickSupport] selectedNodes]; + [self setSelectedNodes:[[sel allObjects] mutableCopy]]; + [selectedNodesArrayController setSelectedObjects:selectedNodes]; + if ([sel count] == 1) { + Node *n = [sel anyObject]; + [nodeDataArrayController setContent:[n data]]; + [nodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [nodeDataArrayController setEditable:YES]; + } else if ([sel count] == 0) { + [nodeDataArrayController setContent:noSelection]; + [nodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [nodeDataArrayController setEditable:NO]; + } else { + [nodeDataArrayController setContent:multipleSelection]; + [nodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [nodeDataArrayController setEditable:NO]; + } + } else { + [nodeDataArrayController setContent:noGraph]; + [nodeDataArrayController setEditable:NO]; + } +} + +- (void)updateEdgeFields { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + NSSet *sel = [[[doc graphicsView] pickSupport] selectedEdges]; + [self setSelectedEdges:[[sel allObjects] mutableCopy]]; + [selectedEdgesArrayController setSelectedObjects:selectedEdges]; + if ([sel count] == 1) { + Edge *e = [sel anyObject]; + [edgeDataArrayController setContent:[e data]]; + [edgeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeDataArrayController setEditable:YES]; + if ([e hasEdgeNode]) { + Node *n = [e edgeNode]; + [edgeNodeDataArrayController setContent:[n data]]; + [edgeNodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeNodeDataArrayController setEditable:YES]; + } else { + [edgeNodeDataArrayController setContent:noEdgeNode]; + [edgeNodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeNodeDataArrayController setEditable:NO]; + } + } else if ([sel count] == 0) { + [edgeDataArrayController setContent:noSelection]; + [edgeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeDataArrayController setEditable:NO]; + [edgeNodeDataArrayController setContent:noSelection]; + [edgeNodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeNodeDataArrayController setEditable:NO]; + } else { + [edgeDataArrayController setContent:multipleSelection]; + [edgeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeDataArrayController setEditable:NO]; + [edgeNodeDataArrayController setContent:multipleSelection]; + [edgeNodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeNodeDataArrayController setEditable:NO]; + } + } else { + [edgeDataArrayController setContent:noGraph]; + [edgeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeDataArrayController setEditable:NO]; + [edgeNodeDataArrayController setContent:noGraph]; + [edgeNodeDataArrayController setSelectionIndexes:[NSIndexSet indexSet]]; + [edgeNodeDataArrayController setEditable:NO]; + } +} + +- (void)graphSelectionChanged:(NSNotification*)notification { + [self updateNodeFields]; + [self updateEdgeFields]; + [self updateGraphFields]; +} + +- (void)controlTextDidEndEditing:(NSNotification*)notification { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + if (doc != nil) { + PickSupport *pick = [[doc graphicsView] pickSupport]; + for (Node *n in [pick selectedNodes]) { + [n attachStyleFromTable:[stylePaletteController nodeStyles]]; + } + + for (Edge *e in [pick selectedEdges]) { + [e attachStyleFromTable:[stylePaletteController edgeStyles]]; + } + } + + [self refreshDocument:[notification object]]; +} + +- (void)addPropertyToAC:(NSArrayController*)ac { + [ac addObject:[[GraphElementProperty alloc] initWithPropertyValue:@"val" forKey:@"new_property"]]; + [self refreshDocument:nil]; +} + +- (void)addAtomToAC:(NSArrayController*)ac { + [ac addObject:[[GraphElementProperty alloc] initWithAtomName:@"new_atom"]]; + [self refreshDocument:nil]; +} + +- (void)removeFromAC:(NSArrayController*)ac { + [ac remove:nil]; + [self refreshDocument:nil]; +} + +- (IBAction)addNodeProperty:(id)sender { [self addPropertyToAC:nodeDataArrayController]; } +- (IBAction)addNodeAtom:(id)sender { [self addAtomToAC:nodeDataArrayController]; } +- (IBAction)removeNodeProperty:(id)sender { [self removeFromAC:nodeDataArrayController]; } + +- (IBAction)addGraphProperty:(id)sender { [self addPropertyToAC:graphDataArrayController]; } +- (IBAction)addGraphAtom:(id)sender { [self addAtomToAC:graphDataArrayController]; } +- (IBAction)removeGraphProperty:(id)sender { [self removeFromAC:graphDataArrayController]; } + +- (IBAction)addEdgeProperty:(id)sender { [self addPropertyToAC:edgeDataArrayController]; } +- (IBAction)addEdgeAtom:(id)sender { [self addAtomToAC:edgeDataArrayController]; } +- (IBAction)removeEdgeProperty:(id)sender { [self removeFromAC:edgeDataArrayController]; } + +- (IBAction)addEdgeNodeProperty:(id)sender { [self addPropertyToAC:edgeNodeDataArrayController]; } +- (IBAction)addEdgeNodeAtom:(id)sender { [self addAtomToAC:edgeNodeDataArrayController]; } +- (IBAction)removeEdgeNodeProperty:(id)sender { [self removeFromAC:edgeNodeDataArrayController]; } + +//- (BOOL)enableEdgeDataControls { +// NSDocumentController *dc = [NSDocumentController sharedDocumentController]; +// TikzDocument *doc = (TikzDocument*)[dc currentDocument]; +// +// if (doc != nil) { +// return ([[[[doc graphicsView] pickSupport] selectedEdges] count] == 1); +// } else { +// return NO; +// } +//} +// +//- (BOOL)enableEdgeNodeDataControls { +// NSDocumentController *dc = [NSDocumentController sharedDocumentController]; +// TikzDocument *doc = (TikzDocument*)[dc currentDocument]; +// +// if (doc != nil) { +// PickSupport *pick = [[doc graphicsView] pickSupport]; +// if ([[pick selectedEdges] count] == 1) { +// return ([[[pick selectedEdges] anyObject] hasEdgeNode]); +// } else { +// return NO; +// } +// } else { +// return NO; +// } +//} + +@end diff --git a/tikzit/src/osx/SelectBoxLayer.h b/tikzit/src/osx/SelectBoxLayer.h new file mode 100644 index 0000000..45b43c7 --- /dev/null +++ b/tikzit/src/osx/SelectBoxLayer.h @@ -0,0 +1,22 @@ +// +// SelectBoxLayer.h +// TikZiT +// +// Created by Aleks Kissinger on 14/06/2010. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import <QuartzCore/CoreAnimation.h> + +@interface SelectBoxLayer : CALayer { + BOOL active; + CGRect box; +} + +@property (assign) BOOL active; +@property (assign) NSRect selectBox; + ++ (SelectBoxLayer*)layer; + +@end diff --git a/tikzit/src/osx/SelectBoxLayer.m b/tikzit/src/osx/SelectBoxLayer.m new file mode 100644 index 0000000..c198ffa --- /dev/null +++ b/tikzit/src/osx/SelectBoxLayer.m @@ -0,0 +1,48 @@ +// +// SelectBoxLayer.m +// TikZiT +// +// Created by Aleks Kissinger on 14/06/2010. +// Copyright 2010 __MyCompanyName__. All rights reserved. +// + +#import "SelectBoxLayer.h" + + +@implementation SelectBoxLayer + +@synthesize active; + +- (id)init { + [super init]; + box = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f); + active = NO; + return self; +} + +- (void)setSelectBox:(NSRect)r { + box = NSRectToCGRect(r); +} + +- (NSRect)selectBox { + return NSRectFromCGRect(box); +} + +- (void)drawInContext:(CGContextRef)context { + if (active) { + CGContextAddRect(context, box); + + CGContextSetRGBStrokeColor(context, 0.6, 0.6, 0.6, 1); + CGContextSetRGBFillColor(context, 0.8, 0.8, 0.8, 0.2); + CGContextSetLineWidth(context, 1); + + CGContextSetShouldAntialias(context, NO); + CGContextDrawPath(context, kCGPathFillStroke); + } +} + ++ (SelectBoxLayer*)layer { + return [[[SelectBoxLayer alloc] init] autorelease]; +} + +@end diff --git a/tikzit/src/osx/SelectableCollectionViewItem.h b/tikzit/src/osx/SelectableCollectionViewItem.h new file mode 100644 index 0000000..4a2c571 --- /dev/null +++ b/tikzit/src/osx/SelectableCollectionViewItem.h @@ -0,0 +1,33 @@ +// +// SelectableCollectionViewItem.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 <Cocoa/Cocoa.h> +#import "StylePaletteController.h" + +@interface SelectableCollectionViewItem : NSCollectionViewItem { + IBOutlet StylePaletteController *stylePaletteController; +} + +- (void)setStylePaletteController:(StylePaletteController*)spc; + +@end diff --git a/tikzit/src/osx/SelectableCollectionViewItem.m b/tikzit/src/osx/SelectableCollectionViewItem.m new file mode 100644 index 0000000..880c37b --- /dev/null +++ b/tikzit/src/osx/SelectableCollectionViewItem.m @@ -0,0 +1,54 @@ +// +// SelectableCollectionViewItem.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 "SelectableCollectionViewItem.h" +#import "SelectableNodeView.h" + +@implementation SelectableCollectionViewItem + +- (id)copyWithZone:(NSZone *)zone { + SelectableCollectionViewItem *item = [super copyWithZone:zone]; + [item setStylePaletteController:stylePaletteController]; + return (id)item; +} + +- (void)setSelected:(BOOL)flag { + [super setSelected:flag]; + [(SelectableNodeView*)[self view] setSelected:flag]; + + // only fire this event from the view that lost selection + //if (flag == NO) [stylePaletteController selectionDidChange]; + + [[self view] setNeedsDisplay:YES]; +} + +- (void)setRepresentedObject:(id)object { + [super setRepresentedObject:object]; + [(SelectableNodeView*)[self view] setNodeStyle:(NodeStyle*)object]; +} + +- (void)setStylePaletteController:(StylePaletteController*)spc { + stylePaletteController = spc; +} + +@end diff --git a/tikzit/src/osx/SelectableNodeView.h b/tikzit/src/osx/SelectableNodeView.h new file mode 100644 index 0000000..be7d1a1 --- /dev/null +++ b/tikzit/src/osx/SelectableNodeView.h @@ -0,0 +1,38 @@ +// +// SelectableView.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 <Cocoa/Cocoa.h> +#import "NodeLayer.h" +#import "NodeStyle.h" +#import "NodeStyle+Coder.h" + +@interface SelectableNodeView : NSView { + BOOL selected; + NodeLayer *nodeLayer; +} + +@property (assign) BOOL selected; +@property (retain) NodeStyle *nodeStyle; + + +@end diff --git a/tikzit/src/osx/SelectableNodeView.m b/tikzit/src/osx/SelectableNodeView.m new file mode 100644 index 0000000..6fdd283 --- /dev/null +++ b/tikzit/src/osx/SelectableNodeView.m @@ -0,0 +1,96 @@ +// +// SelectableView.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 "SelectableNodeView.h" +#import "Shape.h" +#import "Transformer.h" + +@implementation SelectableNodeView + +@synthesize selected; + +- (id)initWithFrame:(NSRect)frameRect { + [super initWithFrame:frameRect]; + nodeLayer = nil; + return self; +} + +-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { +// NSLog(@"got draw"); +// CGContextSaveGState(context); +// +// if (selected) { +// CGContextSetRGBStrokeColor(context, 0.61f, 0.735f, 1.0f, 1.0f); +// CGContextSetRGBFillColor(context, 0.61f, 0.735f, 1.0f, 0.5f); +// CGContextSetLineWidth(context, 1.0f); +// +// CGRect box = CGRectMake([layer frame].origin.x + 2, +// [layer frame].origin.y + 2, +// [layer frame].size.width - 4, +// [layer frame].size.height - 4); +// +// //CGContextAddRect(context, box); +// CGContextDrawPath(context, kCGPathFillStroke); +// } +// +// CGContextRestoreGState(context); + + if (nodeLayer!=nil) { + if (![[[self layer] sublayers] containsObject:nodeLayer]) { + [[self layer] addSublayer:nodeLayer]; + NSPoint c = NSMakePoint(CGRectGetMidX([[self layer] frame]), + CGRectGetMidY([[self layer] frame])); + [nodeLayer setCenter:c andAnimateWhen:NO]; + } + + if (selected) [[nodeLayer selection] select]; + else [[nodeLayer selection] deselect]; + + [nodeLayer updateFrame]; + } +} + +- (void)drawRect:(NSRect)rect { + [super drawRect:rect]; +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { return YES; } + +- (void)setNodeStyle:(NodeStyle *)sty { + if (nodeLayer == nil) { + nodeLayer = [[NodeLayer alloc] initWithNode:[Node node] + transformer:[Transformer defaultTransformer]]; + [nodeLayer setRescale:NO]; + } + + [[nodeLayer node] setStyle:sty]; + [nodeLayer updateFrame]; +} + +- (NodeStyle*)nodeStyle { + if (nodeLayer != nil) return [[nodeLayer node] style]; + else return nil; +} + + +@end diff --git a/tikzit/src/osx/StylePaletteController.h b/tikzit/src/osx/StylePaletteController.h new file mode 100644 index 0000000..ed30b58 --- /dev/null +++ b/tikzit/src/osx/StylePaletteController.h @@ -0,0 +1,80 @@ +// +// StylePaletteController.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 <Cocoa/Cocoa.h> +#import "NodeStyle.h" +#import "EdgeStyle.h" + +@class SFBInspectorView; + +@interface StylePaletteController : NSWindowController { + NSMutableArray *nodeStyles; + NSMutableArray *edgeStyles; + IBOutlet NSArrayController *nodeStyleArrayController; + IBOutlet NSArrayController *filteredNodeStyleArrayController; + IBOutlet NSArrayController *edgeStyleArrayController; + IBOutlet NSArrayController *filteredEdgeStyleArrayController; + IBOutlet NSCollectionView *collectionView; + IBOutlet SFBInspectorView *nodeStyleInspectorView; + IBOutlet NSView *nodeStyleView; + IBOutlet NSView *edgeStyleView; + IBOutlet NSPopUpButton *shapeDropdown; + NSString *displayedNodeStyleCategory; + NSString *displayedEdgeStyleCategory; +} + +@property (readonly) NSMutableArray *nodeStyles; +@property (readonly) NSMutableArray *edgeStyles; +@property (readonly) BOOL documentActive; +@property (assign) NodeStyle *activeNodeStyle; +@property (assign) EdgeStyle *activeEdgeStyle; +@property (copy) NSString *displayedNodeStyleCategory; +@property (copy) NSString *displayedEdgeStyleCategory; +@property (readonly) NSPredicate *displayedNodeStylePredicate; +@property (readonly) NSPredicate *displayedEdgeStylePredicate; + +//@property NSString *nodeLabel; + +- (id)initWithWindowNibName:(NSString *)windowNibName + supportDir:(NSString*)supportDir; +- (void)saveStyles:(NSString *)plist; + +- (IBAction)refreshCollection:(id)sender; + +- (IBAction)applyActiveNodeStyle:(id)sender; +- (IBAction)clearActiveNodeStyle:(id)sender; +- (IBAction)addNodeStyle:(id)sender; + +- (IBAction)appleActiveEdgeStyle:(id)sender; +- (IBAction)clearActiveEdgeStyle:(id)sender; +- (IBAction)addEdgeStyle:(id)sender; +- (void)setActiveEdgeStyle:(EdgeStyle*)style; + +- (IBAction)setFillToClosestHashed:(id)sender; +- (IBAction)setStrokeToClosestHashed:(id)sender; + + +//- (IBAction)changeShape:(id)sender; + + +@end diff --git a/tikzit/src/osx/StylePaletteController.m b/tikzit/src/osx/StylePaletteController.m new file mode 100644 index 0000000..8f87bd9 --- /dev/null +++ b/tikzit/src/osx/StylePaletteController.m @@ -0,0 +1,247 @@ +// +// StylePaletteController.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 "StylePaletteController.h" +#import "TikzDocument.h" +#import "SFBInspectors/SFBInspectorView.h" +#import "PickSupport.h" +#import "Node.h" +#import "Edge.h" +#import "NodeStyle.h" +#import "GraphicsView.h" +#import "GraphElementProperty.h" +#import "Shape.h" + +@implementation StylePaletteController + +@synthesize nodeStyles, edgeStyles; + +- (id)initWithWindowNibName:(NSString *)windowNibName + supportDir:(NSString*)supportDir +{ + if (self = [super initWithWindowNibName:windowNibName]) { + NSString *ns = [supportDir stringByAppendingPathComponent:@"nodeStyles.plist"]; + NSString *es = [supportDir stringByAppendingPathComponent:@"edgeStyles.plist"]; + nodeStyles = (NSMutableArray*)[NSKeyedUnarchiver + unarchiveObjectWithFile:ns]; + edgeStyles = (NSMutableArray*)[NSKeyedUnarchiver + unarchiveObjectWithFile:es]; + + if (nodeStyles == nil) nodeStyles = [NSMutableArray array]; + if (edgeStyles == nil) edgeStyles = [NSMutableArray array]; + + [[self window] setLevel:NSNormalWindowLevel]; + [self showWindow:self]; + } + + return self; +} + +- (void)windowDidLoad { + [[self window] setMovableByWindowBackground:YES]; + [shapeDropdown addItemsWithTitles:[[Shape shapeDictionary] allKeys]]; + if ([self activeNodeStyle] != nil) { + [shapeDropdown setTitle:[[self activeNodeStyle] shapeName]]; + } + + [nodeStyleInspectorView addInspectorPane:nodeStyleView + title:@"Node Styles"]; + + [nodeStyleInspectorView addInspectorPane:edgeStyleView + title:@"Edge Styles"]; + + [super windowDidLoad]; +} + +- (void)saveStyles:(NSString*)supportDir { + NSString *ns = [supportDir stringByAppendingPathComponent:@"nodeStyles.plist"]; + NSString *es = [supportDir stringByAppendingPathComponent:@"edgeStyles.plist"]; + [NSKeyedArchiver archiveRootObject:nodeStyles toFile:ns]; + [NSKeyedArchiver archiveRootObject:edgeStyles toFile:es]; +} + +- (IBAction)refreshCollection:(id)sender { + [collectionView setNeedsDisplay:YES]; +} + + +- (BOOL)documentActive { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + return dc.currentDocument != nil; +} + +-(BOOL)collectionView:(NSCollectionView*)collectionView canDragItemsAtIndexes:(NSIndexSet*)indexes withEvent:(NSEvent*)event { + return YES; +} + + +//=========================== +//= setting SVG-safe colors = +//=========================== +- (IBAction)setFillToClosestHashed:(id)sender { + NSArray *sel = [nodeStyleArrayController selectedObjects]; + if ([sel count] != 0) { + NodeStyle *sty = [sel objectAtIndex:0]; + [sty willChangeValueForKey:@"fillColor"]; + [sty willChangeValueForKey:@"fillColorIsKnown"]; + [sty.fillColorRGB setToClosestHashed]; + [sty didChangeValueForKey:@"fillColor"]; + [sty didChangeValueForKey:@"fillColorIsKnown"]; + } +} + +- (IBAction)setStrokeToClosestHashed:(id)sender { + NSArray *sel = [nodeStyleArrayController selectedObjects]; + if ([sel count] != 0) { + NodeStyle *sty = [sel objectAtIndex:0]; + [sty willChangeValueForKey:@"strokeColor"]; + [sty willChangeValueForKey:@"strokeColorIsKnown"]; + [sty.strokeColorRGB setToClosestHashed]; + [sty didChangeValueForKey:@"strokeColor"]; + [sty didChangeValueForKey:@"strokeColorIsKnown"]; + } +} + +//================================================= +//= setting filter predicates for nodes and edges = +//================================================= +- (NSString*)displayedNodeStyleCategory { + return displayedNodeStyleCategory; +} + +- (void)setDisplayedNodeStyleCategory:(NSString *)cat { + [self willChangeValueForKey:@"displayedNodeStylePredicate"]; + displayedNodeStyleCategory = cat; + [self didChangeValueForKey:@"displayedNodeStylePredicate"]; +} + +- (NSString*)displayedEdgeStyleCategory { + return displayedEdgeStyleCategory; +} + +- (void)setDisplayedEdgeStyleCategory:(NSString *)cat { + [self willChangeValueForKey:@"displayedEdgeStylePredicate"]; + displayedEdgeStyleCategory = cat; + [self didChangeValueForKey:@"displayedEdgeStylePredicate"]; +} + +- (NSPredicate*)displayedNodeStylePredicate { + return [NSPredicate predicateWithFormat:@"category == %@", displayedNodeStyleCategory]; +} + +- (NSPredicate*)displayedEdgeStylePredicate { + return [NSPredicate predicateWithFormat:@"category == %@", displayedEdgeStyleCategory]; +} + + +//============================== +//= getting and setting styles = +//============================== + +- (IBAction)applyActiveNodeStyle:(id)sender { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + [[doc graphicsView] applyStyleToSelectedNodes:[self activeNodeStyle]]; + } + + [[doc graphicsView] postSelectionChange]; +} + +- (IBAction)clearActiveNodeStyle:(id)sender { + [self setActiveNodeStyle:nil]; + + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + [[doc graphicsView] applyStyleToSelectedNodes:nil]; + } + + [[doc graphicsView] postSelectionChange]; +} + +- (NodeStyle*)activeNodeStyle { + NSArray *sel = [filteredNodeStyleArrayController selectedObjects]; + if ([sel count] == 0) return nil; + else return [sel objectAtIndex:0]; +} + +- (void)setActiveNodeStyle:(NodeStyle*)style { + if ([nodeStyles containsObject:style]) { + [filteredNodeStyleArrayController setSelectedObjects:[NSArray arrayWithObject:style]]; + } else { + [filteredNodeStyleArrayController setSelectedObjects:[NSArray array]]; + } +} + +- (IBAction)appleActiveEdgeStyle:(id)sender { + NSDocumentController *dc = [NSDocumentController sharedDocumentController]; + TikzDocument *doc = (TikzDocument*)[dc currentDocument]; + + if (doc != nil) { + [[doc graphicsView] applyStyleToSelectedEdges:[self activeEdgeStyle]]; + } +} + +- (IBAction)clearActiveEdgeStyle:(id)sender { + [self setActiveEdgeStyle:nil]; + [self appleActiveEdgeStyle:sender]; +} + +- (EdgeStyle*)activeEdgeStyle { + NSArray *sel = [filteredEdgeStyleArrayController selectedObjects]; + if ([sel count] == 0) return nil; + else return [sel objectAtIndex:0]; +} + +- (void)setActiveEdgeStyle:(EdgeStyle*)style { + if ([edgeStyles containsObject:style]) { + [filteredEdgeStyleArrayController setSelectedObjects:[NSArray arrayWithObject:style]]; + } else { + [filteredEdgeStyleArrayController setSelectedObjects:[NSArray array]]; + } +} + + +//================= +//= adding styles = +//================= + +- (IBAction)addEdgeStyle:(id)sender { + EdgeStyle *sty = [[EdgeStyle alloc] init]; + [sty setCategory:displayedEdgeStyleCategory]; + [edgeStyleArrayController addObject:sty]; + [self setActiveEdgeStyle:sty]; +} + +- (IBAction)addNodeStyle:(id)sender { + NodeStyle *sty = [[NodeStyle alloc] init]; + [sty setCategory:displayedNodeStyleCategory]; + [nodeStyleArrayController addObject:sty]; + [self setActiveNodeStyle:sty]; +} + + +@end diff --git a/tikzit/src/osx/TikzDocument.h b/tikzit/src/osx/TikzDocument.h new file mode 100644 index 0000000..d817b2e --- /dev/null +++ b/tikzit/src/osx/TikzDocument.h @@ -0,0 +1,37 @@ +// +// TikzDocument.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 <Cocoa/Cocoa.h> +#import "GraphicsView.h" +#import "TikzSourceController.h" +#import "PreviewController.h" +#import "GraphicsView.h" + +@interface TikzDocument : NSDocument { + NSString *tikz; +} + +@property (readonly) NSString *tikz; +@property (readonly) GraphicsView *graphicsView; + +@end diff --git a/tikzit/src/osx/TikzDocument.m b/tikzit/src/osx/TikzDocument.m new file mode 100644 index 0000000..ef5908d --- /dev/null +++ b/tikzit/src/osx/TikzDocument.m @@ -0,0 +1,84 @@ +// +// TikzDocument.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 "TikzDocument.h" +#import "TikzWindowController.h" + +@implementation TikzDocument + +@synthesize tikz; + +- (id)init { + self = [super init]; + if (self) { + tikz = nil; + } + return self; +} + +//- (NSString *)windowNibName { +// // Override returning the nib file name of the document +// // If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this method and override -makeWindowControllers instead. +// return @"TikzDocument"; +//} + +- (void)makeWindowControllers { + TikzWindowController *wc = [[TikzWindowController alloc] initWithDocument:self]; + [self addWindowController:wc]; +} + +- (void)windowControllerDidLoadNib:(NSWindowController *) aController { + [super windowControllerDidLoadNib:aController]; + [[self graphicsView] refreshLayers]; + // Add any code here that needs to be executed once the windowController has loaded the document's window. +} + +- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outError { + TikzWindowController *wc = + (TikzWindowController*)[[self windowControllers] objectAtIndex:0]; + NSData *outData = [[[wc tikzSourceController] tikz] dataUsingEncoding:NSUTF8StringEncoding]; + + if ( outError != NULL ) { + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL]; + } + return outData; +} + +- (BOOL)readFromData:(NSData *)data ofType:(NSString *)typeName error:(NSError **)outError { + tikz = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + + if ( outError != NULL ) { + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:NULL]; + } + + return YES; +} + +- (GraphicsView*)graphicsView { + TikzWindowController *wc = + (TikzWindowController*)[[self windowControllers] objectAtIndex:0]; + return [wc graphicsView]; +} + + +@end diff --git a/tikzit/src/osx/TikzParser.h b/tikzit/src/osx/TikzParser.h new file mode 100644 index 0000000..18b183f --- /dev/null +++ b/tikzit/src/osx/TikzParser.h @@ -0,0 +1,66 @@ +// +// TikzParser.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 <Cocoa/Cocoa.h> +#import <ParseKit/ParseKit.h> + +#import "Graph.h" + +@interface TikzParser : NSObject { + PKParser *nodeParser; + PKParser *edgeParser; + PKParser *tikzPictureParser; + PKTokenizer *tokenizer; + + NSString *currentKey; + NSString *currentSourceArrow; + + NSMutableArray *atoms; + NSMutableDictionary *properties; + NSString *leftArrow; + NSString *rightArrow; + + GraphElementData *elementData; + + Graph *graph; + Node *currentNode; + NSMutableDictionary *nodeTable; + + Edge *currentEdge; + BOOL matchingEdgeNode; + NSString *sourceName; + NSString *targName; + + NSPoint bbox1, bbox2; + BOOL bboxFirstPoint; +} + +@property (retain) Graph *graph; + +- (id)init; +- (BOOL)parseNode:(NSString*)str; +- (BOOL)parseEdge:(NSString*)str; +- (BOOL)parseTikzPicture:(NSString*)str forGraph:(Graph*)g; +- (BOOL)parseTikzPicture:(NSString *)str; + +@end diff --git a/tikzit/src/osx/TikzParser.m b/tikzit/src/osx/TikzParser.m new file mode 100644 index 0000000..2bb5a15 --- /dev/null +++ b/tikzit/src/osx/TikzParser.m @@ -0,0 +1,574 @@ +// +// TikzParser.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 "TikzParser.h" + +// custom parsekit extensions +#import "PKNaturalNumber.h" +#import "PKRepetition+RepeatPlus.h" +#import "PKSpecificDelimitedString.h" +#import "PKBalancingDelimitState.h" + +#import "util.h" + +@interface TikzParser () + +- (NSString*)popAllToString:(PKAssembly*)a withSeparator:(NSString*)sep; +- (PKParser*)eatSymbol:(NSString*)s; +- (PKParser*)eatLiteral:(NSString*)s; +- (PKParser*)eatWord; + +// properties +- (void)willMatchProplist:(PKAssembly*)a; +- (void)didMatchProplist:(PKAssembly*)a; +- (void)willMatchProperty:(PKAssembly*)a; +- (void)didMatchArrowSpecDash:(PKAssembly*)a; + +// nodes +- (void)didMatchNodeCommand:(PKAssembly*)a; +- (void)didMatchNodeLabel:(PKAssembly*)a; +- (void)didMatchNode:(PKAssembly*)a; +- (void)didMatchNodeName:(PKAssembly*)a; +- (void)didMatchCoords:(PKAssembly*)a; + +// edges +- (void)didMatchDrawCommand:(PKAssembly *)a; +- (void)didMatchEdge:(PKAssembly*)a; +- (void)didMatchEdgeNodeCommand:(PKAssembly*)a; +- (void)didMatchEdgeNode:(PKAssembly*)a; + +// bounding box +- (void)didMatchPathCommand:(PKAssembly*)a; +- (void)didMatchBoundingBox:(PKAssembly*)a; + +// tikzpicture +- (void)willMatchTikzPicture:(PKAssembly*)a; +- (void)didMatchTikzPicture:(PKAssembly*)a; + +@end + + +@implementation TikzParser + +@synthesize graph; + +- (id)init { + [super init]; + + currentKey = nil; + currentSourceArrow = nil; + tokenizer = [PKTokenizer tokenizer]; + + graph = nil; + currentNode = nil; + currentEdge = nil; + matchingEdgeNode = NO; + + // tweak the tokenizer a bit + [tokenizer.symbolState remove:@"<="]; + [tokenizer.symbolState remove:@">="]; + [tokenizer.symbolState remove:@"{"]; + [tokenizer.symbolState remove:@"}"]; + [tokenizer setTokenizerState:tokenizer.wordState from:'\\' to:'\\']; + [tokenizer.wordState setWordChars:NO from:'-' to:'-']; + tokenizer.delimitState = [[PKBalancingDelimitState alloc] init]; + [tokenizer.delimitState addStartMarker:@"{" + endMarker:@"}" + allowedCharacterSet:nil]; + [tokenizer setTokenizerState:tokenizer.delimitState from:'{' to:'{']; + + /* + + + GRAMMAR FOR TIKZPICTURE + + tikzpicture = '\begin' '{tikzpicture}' + optproplist + expr* + '\end' '{tikzpicture}' + expr = node | edge | boundingbox | layerexpr + layerexpr = '\begin' '{pgfonlayer}' DelimitedString | '\end' '{pgfonlayer}' + + + GRAMMAR FOR PROPERTY LISTS + + optproplist = proplist | Empty; + proplist = '[' property property1* ']'; + property = arrowspec | keyval | atom; + property1 = ',' property; + keyval = key '=' val; + atom = propsym+; + arrowspec = propsym* '-' propsym*; + key = propsym+; + val = propsym+ | QuotedString; + propsym = (Word | Number | '<' | '>'); + + + GRAMMAR FOR NODES + + node = '\node' optproplist name 'at' coords DelimitedString ';'; + nodename = '(' nodeid ')'; + nodeid = Word | NaturalNumber; + coords = '(' Number ',' Number ')'; + + + GRAMMAR FOR EDGES + + edge = '\draw' optproplist nodename 'to' optedgenode ( nodename | selfloop ) ';'; + selfloop = '(' ')'; + optedgenode = Empty | edgenode + edgenode = 'node' optproplist name coords '{' '}' ';'; + + GRAMMAR FOR BOUNDING BOX + + boundingbox = '\path' '[' 'use' 'as' 'bounding' 'box' ']' coords 'rectangle' coords ';' + + */ + + + PKAlternation *nodeid = [PKAlternation alternation]; + nodeid.name = @"node identifier"; + [nodeid add:[PKWord word]]; + [nodeid add:[PKNaturalNumber number]]; + + PKAlternation *propsym = [PKAlternation alternation]; + propsym.name = @"property symbol"; + [propsym add:[PKWord word]]; + [propsym add:[PKNumber number]]; + [propsym add:[PKSymbol symbolWithString:@"<"]]; + [propsym add:[PKSymbol symbolWithString:@">"]]; + + PKSequence *anchor = [PKSequence sequence]; + [anchor add:[self eatSymbol:@"."]]; + [anchor add:[self eatWord]]; + + PKAlternation *optanchor = [PKAlternation alternation]; + [optanchor add:anchor]; + [optanchor add:[PKEmpty empty]]; + + PKSequence *nodename = [PKSequence sequence]; + nodename.name = @"node name"; + [nodename add:[self eatSymbol:@"("]]; + [nodename add:nodeid]; + [nodename add:optanchor]; + [nodename add:[self eatSymbol:@")"]]; + [nodename setAssembler:self selector:@selector(didMatchNodeName:)]; + + PKTrack *coords = [PKTrack track]; + coords.name = @"coordinate definition"; + [coords add:[self eatSymbol:@"("]]; + [coords add:[PKNumber number]]; + [coords add:[self eatSymbol:@","]]; + [coords add:[PKNumber number]]; + [coords add:[self eatSymbol:@")"]]; + [coords setAssembler:self selector:@selector(didMatchCoords:)]; + + PKSequence *key = [PKRepetition repetitionPlusWithSubparser:propsym]; + + PKAlternation *val = [PKAlternation alternation]; + [val add:[PKRepetition repetitionPlusWithSubparser:propsym]]; + [val add:[PKQuotedString quotedString]]; + [val setPreassembler:self selector:@selector(willMatchVal:)]; + + PKSequence *keyval = [PKSequence sequence]; + [keyval add:key]; + [keyval add:[self eatLiteral:@"="]]; + [keyval add:val]; + + PKSequence *atom = [PKRepetition repetitionPlusWithSubparser:propsym]; + + PKSymbol *arrowspecdash = [PKSymbol symbolWithString:@"-"]; + [arrowspecdash setAssembler:self selector:@selector(didMatchArrowSpecDash:)]; + + PKSequence *arrowspec = [PKSequence sequence]; + [arrowspec add:[PKRepetition repetitionWithSubparser:propsym]]; + [arrowspec add:arrowspecdash]; + [arrowspec add:[PKRepetition repetitionWithSubparser:propsym]]; + + PKAlternation *property = [PKAlternation alternation]; + property.name = @"property, atom, or arrow specification"; + [property add:keyval]; + [property add:arrowspec]; + [property add:atom]; + [property setPreassembler:self selector:@selector(willMatchProperty:)]; + + PKSequence *property1 = [PKSequence sequence]; + [property1 add:[self eatLiteral:@","]]; + [property1 add:property]; + + PKTrack *proplist = [PKTrack track]; + proplist.name = @"property list"; + [proplist add:[self eatSymbol:@"["]]; + [proplist add:property]; + [proplist add:[PKRepetition repetitionWithSubparser:property1]]; + [proplist add:[self eatSymbol:@"]"]]; + [proplist setPreassembler:self selector:@selector(willMatchProplist:)]; + [proplist setAssembler:self selector:@selector(didMatchProplist:)]; + + PKAlternation *optproplist = [PKAlternation alternation]; + [optproplist add:proplist]; + [optproplist add:[PKEmpty empty]]; + + PKLiteral *nodeCommand = [PKLiteral literalWithString:@"\\node"]; + [nodeCommand setAssembler:self selector:@selector(didMatchNodeCommand:)]; + + PKTerminal *nodeLabel = [PKDelimitedString delimitedString]; + nodeLabel.name = @"Possibly empty node label"; + [nodeLabel setAssembler:self selector:@selector(didMatchNodeLabel:)]; + + PKTrack *node = [PKTrack track]; + [node add:nodeCommand]; + [node add:optproplist]; + [node add:nodename]; + [node add:[self eatLiteral:@"at"]]; + [node add:coords]; + [node add:nodeLabel]; + //[node add:[[PKDelimitedString delimitedString] discard]]; + [node add:[self eatSymbol:@";"]]; + [node setAssembler:self selector:@selector(didMatchNode:)]; + + PKLiteral *drawCommand = [PKLiteral literalWithString:@"\\draw"]; + [drawCommand setAssembler:self selector:@selector(didMatchDrawCommand:)]; + + PKSequence *parens = [PKSequence sequence]; + [parens add:[self eatSymbol:@"("]]; + [parens add:[self eatSymbol:@")"]]; + + PKAlternation *nodenamealt = [PKAlternation alternation]; + nodenamealt.name = @"node name or '()'"; + [nodenamealt add:nodename]; + [nodenamealt add:parens]; + + PKLiteral *edgenodeCommand = [PKLiteral literalWithString:@"node"]; + edgenodeCommand.name = @"edge node command"; + [edgenodeCommand setAssembler:self selector:@selector(didMatchEdgeNodeCommand:)]; + + PKSequence *edgenode = [PKSequence sequence]; + [edgenode add:edgenodeCommand]; + [edgenode add:optproplist]; + [edgenode add:nodeLabel]; + edgenode.name = @"edge node"; + [edgenode setAssembler:self selector:@selector(didMatchEdgeNode:)]; + + + PKAlternation *optedgenode = [PKAlternation alternation]; + [optedgenode add:[PKEmpty empty]]; + [optedgenode add:edgenode]; + + + PKTrack *edge = [PKTrack track]; + [edge add:drawCommand]; + [edge add:optproplist]; + [edge add:nodename]; + [edge add:[self eatLiteral:@"to"]]; + [edge add:optedgenode]; + [edge add:nodenamealt]; + [edge add:[self eatSymbol:@";"]]; + [edge setAssembler:self selector:@selector(didMatchEdge:)]; + + + PKLiteral *pathliteral = [PKLiteral literalWithString:@"\\path"]; + [pathliteral setAssembler:self selector:@selector(didMatchPathCommand:)]; + + PKTrack *boundingbox = [PKTrack track]; + [boundingbox add:pathliteral]; + [boundingbox add:[self eatSymbol:@"["]]; + [boundingbox add:[self eatLiteral:@"use"]]; + [boundingbox add:[self eatLiteral:@"as"]]; + [boundingbox add:[self eatLiteral:@"bounding"]]; + [boundingbox add:[self eatLiteral:@"box"]]; + [boundingbox add:[self eatSymbol:@"]"]]; + [boundingbox add:coords]; + [boundingbox add:[self eatLiteral:@"rectangle"]]; + [boundingbox add:coords]; + [boundingbox add:[self eatSymbol:@";"]]; + [boundingbox setAssembler:self selector:@selector(didMatchBoundingBox:)]; + + PKTerminal *layerLiteral = + [[PKSpecificDelimitedString delimitedStringWithValue:@"{pgfonlayer}"] + discard]; + + PKSequence *beginLayer = [PKSequence sequence]; + [beginLayer add:[self eatLiteral:@"\\begin"]]; + [beginLayer add:layerLiteral]; + [beginLayer add:[[PKDelimitedString delimitedString] discard]]; + + PKSequence *endLayer = [PKSequence sequence]; + [endLayer add:[self eatLiteral:@"\\end"]]; + [endLayer add:layerLiteral]; + + PKAlternation *expr = [PKAlternation alternation]; + [expr add:node]; + [expr add:edge]; + [expr add:boundingbox]; + [expr add:beginLayer]; + [expr add:endLayer]; + + PKTerminal *tikzpicLiteral = + [[PKSpecificDelimitedString delimitedStringWithValue:@"{tikzpicture}"] + discard]; + + //tikzpicLiteral.name = @"{tikzpicture}"; + + PKTrack *tikzpic = [PKTrack track]; + [tikzpic add:[PKEmpty empty]]; + [tikzpic add:[self eatLiteral:@"\\begin"]]; + [tikzpic add:tikzpicLiteral]; + [tikzpic add:optproplist]; + [tikzpic add:[PKRepetition repetitionWithSubparser:expr]]; + [tikzpic add:[self eatLiteral:@"\\end"]]; + [tikzpic add:tikzpicLiteral]; + [tikzpic setPreassembler:self selector:@selector(willMatchTikzPicture:)]; + [tikzpic setAssembler:self selector:@selector(didMatchTikzPicture:)]; + + nodeParser = node; + edgeParser = edge; + tikzPictureParser = tikzpic; + + return self; +} + +- (NSString*)popAllToString:(PKAssembly*)a withSeparator:(NSString*)sep { + NSString *str = @""; + BOOL fst = YES; + + while (![a isStackEmpty]) { + if (fst) fst = NO; + else str = [sep stringByAppendingString:str]; + + PKToken *tok = [a pop]; + str = [tok.stringValue stringByAppendingString:str]; + } + + return str; +} + +- (PKParser*)eatSymbol:(NSString*)s { + return [[PKSymbol symbolWithString:s] discard]; +} + +- (PKParser*)eatLiteral:(NSString*)s { + return [[PKLiteral literalWithString:s] discard]; +} + +- (PKParser*)eatWord { + return [[PKWord word] discard]; +} + +- (void)packProperty:(PKAssembly*)a { + BOOL empty = [a isStackEmpty]; + NSString *val = [self popAllToString:a withSeparator:@" "]; + + if (currentKey != nil) { + [elementData setProperty:val forKey:currentKey]; +// NSLog(@" keyval: (%@) => (%@)", currentKey, val); + } else if (currentSourceArrow != nil) { + [elementData setArrowSpecFrom:currentSourceArrow to:val]; +// NSLog(@" arrowspec: (%@-%@)", currentSourceArrow, val); + } else if (!empty) { + [elementData setAtom:val]; +// NSLog(@" atom: (%@)", val); + } + + currentKey = nil; + currentSourceArrow = nil; +} + +- (BOOL)parseNode:(NSString *)str { + tokenizer.string = str; + PKAssembly *res = [nodeParser completeMatchFor:[PKTokenAssembly assemblyWithTokenizer:tokenizer]]; + +// NSLog(@"result: %@", res); + return res != nil; +} + +- (BOOL)parseEdge:(NSString *)str { + tokenizer.string = str; + PKAssembly *res = [edgeParser completeMatchFor:[PKTokenAssembly assemblyWithTokenizer:tokenizer]]; + +// NSLog(@"result: %@", res); + return res != nil; +} + +- (BOOL)parseTikzPicture:(NSString*)str forGraph:(Graph*)g { + self.graph = g; + tokenizer.string = str; + PKTokenAssembly *assm = [PKTokenAssembly assemblyWithTokenizer:tokenizer]; + PKAssembly *res = [tikzPictureParser completeMatchFor:assm]; + +// NSLog(@"result: %@", res); + return res != nil; +} + +- (BOOL)parseTikzPicture:(NSString *)str { + return [self parseTikzPicture:str forGraph:[Graph graph]]; +} + + +- (void)didMatchNodeCommand:(PKAssembly*)a { + [a pop]; + currentNode = [Node node]; + [currentNode updateData]; +// NSLog(@"<node>"); +} + +- (void)didMatchNodeLabel:(PKAssembly*)a { + PKToken *tok = [a pop]; + NSString *s = tok.stringValue; + s = [s substringWithRange:NSMakeRange(1, [s length]-2)]; + if (matchingEdgeNode) currentEdge.edgeNode.label = s; + else currentNode.label = s; +} + +- (void)didMatchNode:(PKAssembly*)a { + [nodeTable setObject:currentNode forKey:currentNode.name]; + [graph addNode:currentNode]; + currentNode = nil; +// NSLog(@"</node>"); +} + +- (void)didMatchDrawCommand:(PKAssembly*)a { + [a pop]; + currentEdge = [Edge edge]; + sourceName = nil; + targName = nil; +// NSLog(@"<edge>"); +} + +- (void)didMatchEdge:(PKAssembly*)a { + Node *src = [nodeTable objectForKey:sourceName]; + currentEdge.source = src; + currentEdge.target = (targName == nil) ? src : [nodeTable objectForKey:targName]; + [currentEdge setAttributesFromData]; + [graph addEdge:currentEdge]; + currentEdge = nil; +// NSLog(@"</edge>"); +} + +- (void)didMatchEdgeNodeCommand:(PKAssembly*)a { + [a pop]; + matchingEdgeNode = YES; + currentEdge.edgeNode = [Node node]; +} + +- (void)didMatchEdgeNode:(PKAssembly*)a { + matchingEdgeNode = NO; +} + +- (void)willMatchVal:(PKAssembly*)a { + currentKey = [self popAllToString:a withSeparator:@" "]; +// NSLog(@"key: %@", currentKey); +} + +- (void)willMatchProperty:(PKAssembly*)a { + [self packProperty:a]; +} + +- (void)willMatchProplist:(PKAssembly*)a { + elementData = [[GraphElementData alloc] init]; +} + +- (void)didMatchProplist:(PKAssembly*)a { + [self packProperty:a]; + + if (currentNode != nil) { + currentNode.data = elementData; + } else if (currentEdge != nil) { + if (matchingEdgeNode) currentEdge.edgeNode.data = elementData; + else currentEdge.data = elementData; + } else { // add properties to to graph + graph.data = elementData; + } + + elementData = nil; +} + +- (void)didMatchNodeName:(PKAssembly*)a { + NSString *name = ((PKToken*)[a pop]).stringValue; + + if (currentNode != nil) { + currentNode.name = name; +// NSLog(@" name: (%@)", name); + } else if (currentEdge != nil) { + if (sourceName == nil) { + sourceName = name; +// NSLog(@" source: (%@)", name); + } else { + targName = name; +// NSLog(@" target: (%@)", name); + } + } +} + +- (void)didMatchCoords:(PKAssembly*)a { + NSPoint p; + p.y = ((PKToken*)[a pop]).floatValue; + p.x = ((PKToken*)[a pop]).floatValue; + + if (currentNode != nil) { + currentNode.point = p; + } else { + if (bboxFirstPoint) { + bboxFirstPoint = NO; + bbox1 = p; + } else { + bbox2 = p; + } + } + +// NSLog(@" coord: (%f, %f)", p.x, p.y); +} + +- (void)didMatchPathCommand:(PKAssembly*)a { + [a pop]; + bboxFirstPoint = YES; +} + +- (void)didMatchBoundingBox:(PKAssembly*)a { + graph.boundingBox = NSRectAroundPoints(bbox1, bbox2); +} + +- (void)didMatchArrowSpecDash:(PKAssembly*)a { + [a pop]; // pop off the dash + currentSourceArrow = [self popAllToString:a withSeparator:@" "]; +} + +- (void)willMatchTikzPicture:(PKAssembly*)a { +// NSLog(@"<tikz>"); + nodeTable = [NSMutableDictionary dictionary]; +} + +- (void)didMatchTikzPicture:(PKAssembly*)a { +// NSLog(@"</tikz>"); +// NSLog(@"%@", [graph tikz]); +} + +- (void)finalize { +// NSLog(@"releasing subparser trees"); + PKReleaseSubparserTree(nodeParser); + PKReleaseSubparserTree(edgeParser); + PKReleaseSubparserTree(tikzPictureParser); + [super finalize]; +} + +@end diff --git a/tikzit/src/osx/TikzSourceController.h b/tikzit/src/osx/TikzSourceController.h new file mode 100644 index 0000000..01c2cf3 --- /dev/null +++ b/tikzit/src/osx/TikzSourceController.h @@ -0,0 +1,66 @@ +// +// TikzSourceController.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 <Cocoa/Cocoa.h> + +#import "GraphicsView.h" +#import "TikzGraphAssembler.h" + +@interface TikzSourceController : NSObject { + GraphicsView *graphicsView; + NSTextView *sourceView; + NSAttributedString *source; + NSTextField *status; + NSDictionary *textAttrs; + NSColor *successColor; + NSColor *failedColor; + + + NSUndoManager *documentUndoManager; + + BOOL tikzChanged; + BOOL justUndid; + + TikzGraphAssembler *assembler; +} + +@property BOOL tikzChanged; +@property IBOutlet GraphicsView *graphicsView; +@property IBOutlet NSTextView *sourceView; +@property IBOutlet NSTextField *status; +@property NSUndoManager *documentUndoManager; +@property (copy) NSAttributedString *source; +@property (copy) NSString *tikz; + +- (void)updateTikzFromGraph; +- (void)graphChanged:(NSNotification*)n; + +// called by code, these do not register an undo +- (BOOL)tryParseTikz; +- (void)doRevertTikz; + +// called by user, these do register an undo +- (void)parseTikz:(id)sender; +- (void)revertTikz:(id)sender; + +@end diff --git a/tikzit/src/osx/TikzSourceController.m b/tikzit/src/osx/TikzSourceController.m new file mode 100644 index 0000000..6d1580c --- /dev/null +++ b/tikzit/src/osx/TikzSourceController.m @@ -0,0 +1,196 @@ +// +// TikzSourceController.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 "TikzSourceController.h" +#import "Graph.h" + +@implementation TikzSourceController + +@synthesize graphicsView, sourceView, source, status; +@synthesize documentUndoManager, tikzChanged; + +- (void)endEditing { + NSResponder *res = [[sourceView window] firstResponder]; + [[sourceView window] makeFirstResponder:nil]; + [[sourceView window] makeFirstResponder:res]; +} + +- (void)undoParseTikz:(Graph *)oldGraph { + [graphicsView setGraph:oldGraph]; + [graphicsView setEnabled:NO]; + [graphicsView postGraphChange]; + [graphicsView refreshLayers]; + + [documentUndoManager registerUndoWithTarget:self + selector:@selector(parseTikz:) + object:self]; + [documentUndoManager setActionName:@"Parse Tikz"]; +} + +- (void)undoRevertTikz:(NSString*)oldTikz { + [self setTikz:oldTikz]; + [graphicsView setEnabled:NO]; + [graphicsView refreshLayers]; + + [documentUndoManager registerUndoWithTarget:self + selector:@selector(revertTikz:) + object:self]; + [documentUndoManager setActionName:@"Revert Tikz"]; +} + +- (void)undoTikzChange:(id)ignore { + [graphicsView setEnabled:YES]; + [graphicsView refreshLayers]; + [self endEditing]; + [self updateTikzFromGraph]; + [documentUndoManager registerUndoWithTarget:self + selector:@selector(redoTikzChange:) + object:nil]; + [documentUndoManager setActionName:@"Tikz Change"]; +} + +- (void)redoTikzChange:(id)ignore { + [graphicsView setEnabled:NO]; + [graphicsView refreshLayers]; + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoTikzChange:) + object:nil]; + [documentUndoManager setActionName:@"Tikz Change"]; +} + + +- (void)awakeFromNib { + justUndid = NO; + assembler = [[TikzGraphAssembler alloc] init]; + + successColor = [NSColor colorWithCalibratedRed:0.0f + green:0.5f + blue:0.0f + alpha:1.0f]; + failedColor = [NSColor redColor]; + + NSFont *font = [NSFont userFixedPitchFontOfSize:11.0f]; + + if (font != nil) { + textAttrs = [NSDictionary dictionaryWithObject:font + forKey:NSFontAttributeName]; + } else { + NSLog(@"WARNING: couldn't find monospaced font."); + textAttrs = [NSDictionary dictionary]; + } + + + [self graphChanged:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChanged" + object:graphicsView]; +} + +- (void)setTikz:(NSString *)str { + [self willChangeValueForKey:@"source"]; + source = [[NSAttributedString alloc] initWithString:str attributes:textAttrs]; + [self didChangeValueForKey:@"source"]; +} + +- (NSString*)tikz { + return [source string]; +} + +- (void)updateTikzFromGraph { + [self setTikz:[[graphicsView graph] tikz]]; +} + +- (void)graphChanged:(NSNotification*)n { + if ([graphicsView enabled]) [self updateTikzFromGraph]; +} + +- (void)textDidBeginEditing:(NSNotification *)notification { + if ([graphicsView enabled]) { + [graphicsView setEnabled:NO]; + [graphicsView refreshLayers]; + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoTikzChange:) + object:nil]; + [documentUndoManager setActionName:@"Tikz Change"]; + } +} + +- (BOOL)tryParseTikz { + BOOL success = [assembler parseTikz:[self tikz]]; + + if (success) { + [graphicsView deselectAll:self]; + [graphicsView setGraph:[assembler graph]]; + [graphicsView refreshLayers]; + [self doRevertTikz]; + } + + return success; +} + +- (void)doRevertTikz { + [self updateTikzFromGraph]; + [self endEditing]; + [graphicsView setEnabled:YES]; + [graphicsView refreshLayers]; + [status setStringValue:@""]; +} + +- (void)parseTikz:(id)sender { + if (![graphicsView enabled]) { + Graph *oldGraph = [graphicsView graph]; + if ([self tryParseTikz]) { + [self endEditing]; + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoParseTikz:) + object:oldGraph]; + [documentUndoManager setActionName:@"Parse Tikz"]; + + [status setStringValue:@"success"]; + [status setTextColor:successColor]; + } else { + [status setStringValue:@"parse error"]; + [status setTextColor:failedColor]; + } + } +} + +- (void)revertTikz:(id)sender { + if (![graphicsView enabled]) { + NSString *oldTikz = [[self tikz] copy]; + [self doRevertTikz]; + + [documentUndoManager registerUndoWithTarget:self + selector:@selector(undoRevertTikz:) + object:oldTikz]; + [documentUndoManager setActionName:@"Revert Tikz"]; + } +} + +- (void)finalize { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super finalize]; +} + +@end diff --git a/tikzit/src/osx/TikzWindowController.h b/tikzit/src/osx/TikzWindowController.h new file mode 100644 index 0000000..e35b7eb --- /dev/null +++ b/tikzit/src/osx/TikzWindowController.h @@ -0,0 +1,31 @@ +// +// TikzWindowController.h +// TikZiT +// +// Created by Aleks Kissinger on 26/01/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@class TikzDocument, GraphicsView, TikzSourceController; + +@interface TikzWindowController : NSWindowController { + GraphicsView *graphicsView; + TikzSourceController *tikzSourceController; + TikzDocument *document; +} + +@property IBOutlet GraphicsView *graphicsView; +@property IBOutlet TikzSourceController *tikzSourceController; + +- (id)initWithDocument:(TikzDocument*)doc; + +// pass these straight to the tikz source controller +- (void)parseTikz:(id)sender; +- (void)revertTikz:(id)sender; +- (void)zoomIn:(id)sender; +- (void)zoomOut:(id)sender; +- (void)zoomToActualSize:(id)sender; + +@end diff --git a/tikzit/src/osx/TikzWindowController.m b/tikzit/src/osx/TikzWindowController.m new file mode 100644 index 0000000..2e672d2 --- /dev/null +++ b/tikzit/src/osx/TikzWindowController.m @@ -0,0 +1,66 @@ +// +// TikzWindowController.m +// TikZiT +// +// Created by Aleks Kissinger on 26/01/2011. +// Copyright 2011 __MyCompanyName__. All rights reserved. +// + +#import "TikzWindowController.h" +#import "TikzDocument.h" +#import "GraphicsView.h" +#import "TikzSourceController.h" + +@implementation TikzWindowController + +@synthesize graphicsView, tikzSourceController; + +- (id)initWithDocument:(TikzDocument*)doc { + [super initWithWindowNibName:@"TikzDocument"]; + document = doc; + return self; +} + +- (void)awakeFromNib { + if ([document tikz] != nil) { + [graphicsView setEnabled:NO]; + [tikzSourceController setTikz:[document tikz]]; + [tikzSourceController parseTikz:self]; + } + + [graphicsView setDocumentUndoManager:[document undoManager]]; + [tikzSourceController setDocumentUndoManager:[document undoManager]]; +} + +- (void)parseTikz:(id)sender { + [tikzSourceController parseTikz:sender]; +} + +- (void)revertTikz:(id)sender { + [tikzSourceController revertTikz:sender]; +} + +- (void)previewTikz:(id)sender { + PreviewController *pc = [PreviewController defaultPreviewController]; + if (![[pc window] isVisible]) [pc showWindow:sender]; + [pc buildTikz:[tikzSourceController tikz]]; +} + +- (void)zoomIn:(id)sender { + float scale = [[graphicsView transformer] scale] * 1.25f; + [[graphicsView transformer] setScale:scale]; + [graphicsView refreshLayers]; +} + +- (void)zoomOut:(id)sender { + float scale = [[graphicsView transformer] scale] * 0.8f; + [[graphicsView transformer] setScale:scale]; + [graphicsView refreshLayers]; +} + +- (void)zoomToActualSize:(id)sender { + [[graphicsView transformer] setScale:50.0f]; + [graphicsView refreshLayers]; +} + +@end diff --git a/tikzit/src/osx/ToolPaletteController.h b/tikzit/src/osx/ToolPaletteController.h new file mode 100644 index 0000000..e45c08d --- /dev/null +++ b/tikzit/src/osx/ToolPaletteController.h @@ -0,0 +1,42 @@ +// +// ToolPaletteController.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 <Cocoa/Cocoa.h> + +typedef enum { + TikzToolSelect, + TikzToolNode, + TikzToolEdge, + TikzToolCrop +} TikzTool; + +@interface ToolPaletteController : NSObject { + NSPanel *toolPalette; + NSMatrix *toolMatrix; +} + +@property TikzTool selectedTool; +@property IBOutlet NSPanel *toolPalette; +@property IBOutlet NSMatrix *toolMatrix; + + +@end diff --git a/tikzit/src/osx/ToolPaletteController.m b/tikzit/src/osx/ToolPaletteController.m new file mode 100644 index 0000000..000287d --- /dev/null +++ b/tikzit/src/osx/ToolPaletteController.m @@ -0,0 +1,58 @@ +// +// ToolPaletteController.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 "ToolPaletteController.h" + + +@implementation ToolPaletteController + +@synthesize toolPalette, toolMatrix; + +- (TikzTool)selectedTool { + switch (toolMatrix.selectedRow) { + case 0: return TikzToolSelect; + case 1: return TikzToolNode; + case 2: return TikzToolEdge; + case 3: return TikzToolCrop; + } + return TikzToolSelect; +} + +- (void)setSelectedTool:(TikzTool)tool { + switch (tool) { + case TikzToolSelect: + [toolMatrix selectCellAtRow:0 column:0]; + break; + case TikzToolNode: + [toolMatrix selectCellAtRow:1 column:0]; + break; + case TikzToolEdge: + [toolMatrix selectCellAtRow:2 column:0]; + break; + case TikzToolCrop: + [toolMatrix selectCellAtRow:3 column:0]; + break; + } +} + +@end diff --git a/tikzit/src/osx/main.m b/tikzit/src/osx/main.m new file mode 100644 index 0000000..e6b4499 --- /dev/null +++ b/tikzit/src/osx/main.m @@ -0,0 +1,26 @@ +// +// main.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 <Cocoa/Cocoa.h> + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/tikzit/src/osx/test/main.m b/tikzit/src/osx/test/main.m new file mode 100644 index 0000000..ad0c1f7 --- /dev/null +++ b/tikzit/src/osx/test/main.m @@ -0,0 +1,56 @@ +// +// main.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" +#include <string.h> + +void testCommon(); +void testOSX(); + +int main(int argc, char **argv) { + if (argc == 2 && strcmp(argv[1], "--disable-color")==0) { + setColorEnabled(NO); + } else { + setColorEnabled(YES); + } + + NSBundle *bund = [NSBundle bundleWithPath:@"TikZiT.app"]; + + + PUTS(@""); + PUTS(@"**********************************************"); + PUTS(@"TikZiT TESTS, OS X BUNDLE VERSION %@", + [bund objectForInfoDictionaryKey:@"CFBundleVersion"]); + PUTS(@"**********************************************"); + PUTS(@""); + + startTests(); + testCommon(); + testOSX(); + + PUTS(@""); + PUTS(@"**********************************************"); + endTests(); + PUTS(@"**********************************************"); + PUTS(@""); +}
\ No newline at end of file diff --git a/tikzit/src/osx/test/osx.m b/tikzit/src/osx/test/osx.m new file mode 100644 index 0000000..f9565ab --- /dev/null +++ b/tikzit/src/osx/test/osx.m @@ -0,0 +1,64 @@ +// +// osx.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 <Cocoa/Cocoa.h> + +void testOSX() { +// char template[] = "/tmp/tikzit_test_tmp_XXXXXXX"; +// char *dir = mkdtemp(template); +// NSString *tempDir = [NSString stringWithUTF8String:dir]; +// +// NSString *testLatex = +// @"\\documentclass{article}\n" +// @"\\begin{document}\n" +// @"test document\n" +// @"\\end{document}\n"; +// +// NSString *texFile = [NSString stringWithFormat:@"%@/test.tex", tempDir]; +// NSString *pdfFile = [NSString stringWithFormat:@"%@/test.pdf", tempDir]; +// +// [testLatex writeToFile:texFile atomically:NO encoding:NSUTF8StringEncoding error:NULL]; +// +// NSTask *task = [[NSTask alloc] init]; +// [task setLaunchPath:@"/bin/bash"]; +// NSPipe *inpt = [NSPipe pipe]; +// NSPipe *outpt = [NSPipe pipe]; +// [task setStandardInput:inpt]; +// [task setStandardOutput:outpt]; +// +// [task launch]; +// +// NSFileHandle *wr = [inpt fileHandleForWriting]; +// NSString *cmd = +// [NSString stringWithFormat: +// @"if [ -e ~/.profile ]; then source ~/.profile; fi" +// @"if [ -e ~/.profile ]; then source ~/.profile; fi"; +// [wr writeData:[cmd dataUsingEncoding:NSUTF8StringEncoding]]; +// [wr closeFile]; +// +// NSFileHandle *rd = [outpt fileHandleForReading]; +// NSString *res = [[NSString alloc] initWithData:[rd readDataToEndOfFile] +// encoding:NSUTF8StringEncoding]; +// NSLog(@"got:\n %@", res); +} diff --git a/tikzit/src/tikzit.rc b/tikzit/src/tikzit.rc new file mode 100644 index 0000000..072f825 --- /dev/null +++ b/tikzit/src/tikzit.rc @@ -0,0 +1,24 @@ +1 VERSIONINFO +FILEVERSION 0,7,0,0 +PRODUCTVERSION 0,7,0,0 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904E4" + BEGIN + VALUE "FileDescription", "A graph editor for LaTeX" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "tikzit" + VALUE "LegalCopyright", "Aleks Kissinger, Alex Merry, Chris Heunen" + VALUE "OriginalFilename", "tikzit.exe" + VALUE "ProductName", "TikZiT" + VALUE "ProductVersion", "0.7" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1252 + END +END +id ICON "../tikzit.ico" |